, restartServer: function}} options
+ */
+ function init(options) {
+ _options = options || {};
+ EditorManager.on("activeEditorChange.tsCodeIntel", function (evt, current) {
+ _autoEnable(current);
+ _updatePanelRow(); // the row depends on the active file's language
+ });
+ // Also catch switches to non-editor views (image/preview) where activeEditorChange may not fire.
+ MainViewManager.on("currentFileChange.tsCodeIntel", _updatePanelRow);
+ // A pending toast belongs to the project that was open when it appeared - drop it on switch;
+ // also refresh the Problems-panel re-enable row for the new project.
+ ProjectManager.on(ProjectManager.EVENT_PROJECT_OPEN + ".tsCodeIntel", function () {
+ _dismissActiveNotification();
+ _updatePanelRow();
+ });
+ // Evaluate the file already showing at startup (the common "restored a JS file" case).
+ _autoEnable(EditorManager.getActiveEditor());
+ _updatePanelRow();
+ }
+
+ exports.init = init;
+ exports.promptEnable = promptEnable;
+});
diff --git a/src/extensions/default/TypeScriptSupport/main.js b/src/extensions/default/TypeScriptSupport/main.js
index 16e5b11032..3ab4165db3 100644
--- a/src/extensions/default/TypeScriptSupport/main.js
+++ b/src/extensions/default/TypeScriptSupport/main.js
@@ -34,7 +34,8 @@ define(function (require, exports, module) {
ProjectManager = brackets.getModule("project/ProjectManager"),
DocumentManager = brackets.getModule("document/DocumentManager"),
FileSystem = brackets.getModule("filesystem/FileSystem"),
- NodeConnector = brackets.getModule("NodeConnector");
+ NodeConnector = brackets.getModule("NodeConnector"),
+ CodeIntelligence = require("./CodeIntelligence");
const SERVER_ID = "typescript";
const SUPPORTED_LANGUAGES = ["javascript", "typescript", "jsx", "tsx"];
@@ -269,13 +270,28 @@ define(function (require, exports, module) {
window._TypeScriptSupportReadyToIntegTest = true;
});
- // Restart the server against the new workspace root when the project changes, and
- // re-evaluate whether the new project type-checks its JS.
+ // Offer project-wide code intelligence (creates a default ts/jsconfig) when a JS/TS file is
+ // opened in a project that has no config yet. Projects that already carry one are silent.
+ CodeIntelligence.init({
+ supportedLanguages: SUPPORTED_LANGUAGES,
+ restartServer: function () {
+ if (registered) {
+ loadLSPClient().then(function (LSPClient) {
+ LSPClient.restartLanguageServer(SERVER_ID);
+ });
+ }
+ }
+ });
+
+ // Re-point the server at the new workspace root when the project changes, and re-evaluate
+ // whether the new project type-checks its JS. This uses workspace/didChangeWorkspaceFolders
+ // (no process restart, so no tsserver cold start) and only falls back to a full restart for
+ // servers that don't support live workspace-folder changes.
ProjectManager.on(ProjectManager.EVENT_PROJECT_OPEN, function () {
_refreshCheckJs();
if (registered) {
loadLSPClient().then(function (LSPClient) {
- LSPClient.restartLanguageServer(SERVER_ID);
+ LSPClient.changeWorkspaceRoot(SERVER_ID);
});
}
});
diff --git a/src/extensions/default/TypeScriptSupport/unittests.js b/src/extensions/default/TypeScriptSupport/unittests.js
index a2a417e6fc..097a97721e 100644
--- a/src/extensions/default/TypeScriptSupport/unittests.js
+++ b/src/extensions/default/TypeScriptSupport/unittests.js
@@ -59,7 +59,18 @@ define(function (require, exports, module) {
await awaitsFor(function () {
return testWindow._TypeScriptSupportReadyToIntegTest;
}, "TypeScript LSP server to start", 30000);
- }, 30000);
+
+ // Warm up tsserver. Its very first request pays a large one-time cost - spawning node,
+ // launching vtsls, and loading the TypeScript library + project - which on a slow/loaded
+ // CI runner can exceed a single spec's timeout (fast dev machines never see it). Pay it
+ // once here with a generous budget so every spec below talks to an already-primed server;
+ // later project-switch restarts reuse the warm process and are fast.
+ await SpecRunnerUtils.loadProjectInTestWindow(testFolder + "ts/");
+ await awaitsForDone(SpecRunnerUtils.openProjectFiles(["type-error.ts"]), "warm-up: open type-error.ts");
+ await awaitsFor(function () {
+ return $("#problems-panel").text().includes("not assignable");
+ }, "tsserver to warm up on first cold start", 90000);
+ }, 100000);
afterAll(async function () {
testWindow = null;
@@ -85,8 +96,8 @@ define(function (require, exports, module) {
// type-error.ts assigns a string to a `number` -> TS2322 "... not assignable ...".
await awaitsFor(function () {
return panelText().includes("not assignable");
- }, "TypeScript type error to be reported", 20000);
- }, 30000);
+ }, "TypeScript type error to be reported", 30000);
+ }, 45000);
it("should report implicit-any in a JS project that opts into checkJs", async function () {
// js-checkjs has a jsconfig.json with checkJs + noImplicitAny, so the untyped parameter
@@ -94,8 +105,8 @@ define(function (require, exports, module) {
await _openInProject("js-checkjs/", "implicit.js");
await awaitsFor(function () {
return panelText().includes(IMPLICIT_ANY_MESSAGE);
- }, "implicit-any to be reported under checkJs", 20000);
- }, 30000);
+ }, "implicit-any to be reported under checkJs", 30000);
+ }, 45000);
it("should NOT report implicit-any in a plain JS project", async function () {
// Precondition: confirm the server actually produces implicit-any for this exact code
@@ -103,16 +114,16 @@ define(function (require, exports, module) {
await _openInProject("js-checkjs/", "implicit.js");
await awaitsFor(function () {
return panelText().includes(IMPLICIT_ANY_MESSAGE);
- }, "implicit-any under checkJs (precondition)", 20000);
+ }, "implicit-any under checkJs (precondition)", 30000);
// Same code in a plain JS project (no jsconfig / no @ts-check): the "go add types" nag
// must not appear. Wait for inspection to settle clean, then assert it is absent.
await _openInProject("js-plain/", "implicit.js");
await awaitsFor(function () {
return $("#status-inspection").hasClass("inspection-valid");
- }, "plain JS inspection to settle with no problems", 20000);
+ }, "plain JS inspection to settle with no problems", 30000);
expect(panelText().includes(IMPLICIT_ANY_MESSAGE)).toBe(false);
- }, 30000);
+ }, 75000);
// ----- hover quick-actions (Go to Definition / Find Usages) -------------------------------
@@ -138,7 +149,7 @@ define(function (require, exports, module) {
await awaitsFor(async function () {
popover = await _hoverPopoverAt(editor, CALL_LINE, CALL_CH);
return !!(popover && popover.content && popover.content.find(".lsp-hover-action").length === 2);
- }, "hover quick actions to appear", 20000);
+ }, "hover quick actions to appear", 30000);
const labels = popover.content.find(".lsp-hover-action-label").map(function () {
return $(this).text();
@@ -158,16 +169,16 @@ define(function (require, exports, module) {
$act.trigger("click");
}
return EditorManager.getCurrentFullEditor().getCursorPos().line === DECL_LINE;
- }, "Go to Definition to navigate to the declaration", 25000);
+ }, "Go to Definition to navigate to the declaration", 30000);
expect(EditorManager.getCurrentFullEditor().getCursorPos().line).toBe(DECL_LINE);
- }, 40000);
+ }, 75000);
it("hover Find Usages opens the references panel (" + tc.ext + ")", async function () {
await _openInProject(tc.folder, tc.file);
const editor = EditorManager.getCurrentFullEditor();
await awaitsFor(async function () {
return !!(await _hoverPopoverAt(editor, CALL_LINE, CALL_CH));
- }, "hover popover to be available", 20000);
+ }, "hover popover to be available", 30000);
// "Find Usages" is the right-aligned action; clicking it opens the references panel.
// Retry through the hover until the panel opens (the server may still be indexing).
@@ -181,9 +192,9 @@ define(function (require, exports, module) {
$end.trigger("click");
}
return $("#reference-in-files-results").is(":visible");
- }, "references panel to open", 25000);
+ }, "references panel to open", 30000);
expect($("#reference-in-files-results").is(":visible")).toBe(true);
- }, 40000);
+ }, 75000);
});
});
});
diff --git a/src/languageTools/DefaultProviders.js b/src/languageTools/DefaultProviders.js
index b5434d475f..af3614a365 100644
--- a/src/languageTools/DefaultProviders.js
+++ b/src/languageTools/DefaultProviders.js
@@ -57,6 +57,30 @@ define(function (require, exports, module) {
}
}
+ // Syntax-highlight fenced code blocks (the signature/examples in completion docs) with the
+ // globally available highlight.js, so they read like code instead of flat monospace. Theme-aware
+ // token colours live in src/styles/brackets.less (.lsp-hint-doc-popup, shared with the hover).
+ function _highlightCode(html) {
+ var hljs = (typeof Phoenix !== "undefined") && Phoenix.libs && Phoenix.libs.hljs;
+ if (!hljs) {
+ return html;
+ }
+ var $wrap = $("").html(html);
+ $wrap.find("pre > code").each(function () {
+ var $code = $(this);
+ var match = ($code.attr("class") || "").match(/language-([\w-]+)/);
+ var lang = match && match[1];
+ if (lang && hljs.getLanguage(lang)) {
+ try {
+ $code.html(hljs.highlight($code.text(), { language: lang }).value).addClass("hljs");
+ } catch (e) {
+ // leave the block unhighlighted on any hljs error
+ }
+ }
+ });
+ return $wrap.html();
+ }
+
function _docToHtml(documentation) {
if (!documentation) {
return "";
@@ -66,7 +90,7 @@ define(function (require, exports, module) {
return "";
}
try {
- return marked.parse(md);
+ return _highlightCode(marked.parse(md));
} catch (e) {
return _.escape(md);
}
diff --git a/src/languageTools/LSPClient.js b/src/languageTools/LSPClient.js
index ba21118bfd..0f39e3f047 100644
--- a/src/languageTools/LSPClient.js
+++ b/src/languageTools/LSPClient.js
@@ -167,6 +167,12 @@ define(function (require, exports, module) {
if (!client) {
return;
}
+ if (client._stopping) {
+ // The server is shutting down/restarting. Ignore any late messages it emits during the
+ // teardown window so stale diagnostics from the dying instance don't leak into the fresh
+ // one that replaces it (both share the same serverId).
+ return;
+ }
if (data.method === "textDocument/publishDiagnostics" && client.lintingProvider) {
const params = data.params || {};
// Rewrite the URI to a VFS-based URI so the linting provider keys results by the
@@ -533,6 +539,11 @@ define(function (require, exports, module) {
const rootVfsPath = (config.rootUriProvider && config.rootUriProvider()) || _projectRootPath();
const rootUri = rootVfsPath ? pathToServerUri(rootVfsPath) : null;
const rootName = rootVfsPath ? FileUtils.getBaseName(rootVfsPath) : "root";
+ // Remember the active workspace folder so a later project switch can hand the server the
+ // delta (removed old, added new) via workspace/didChangeWorkspaceFolders - see
+ // changeWorkspaceRoot - instead of a full restart.
+ client.rootUri = rootUri;
+ client.rootName = rootName;
await conn.execPeer("startServer", {
serverId: client.serverId,
@@ -647,6 +658,74 @@ define(function (require, exports, module) {
* @param {string} serverId
* @return {Promise}
*/
+ // How long to wait for a server to acknowledge a graceful `shutdown` before we hard-kill it.
+ // Healthy servers reply in well under this; the cap is a failsafe so a slow/buggy/hung server
+ // can't stall the restart indefinitely.
+ const SHUTDOWN_TIMEOUT_MS = 3000;
+
+ // Resolve/reject with `promise`, but reject with a timeout error if it doesn't settle in `ms`.
+ function _withTimeout(promise, ms) {
+ return new Promise(function (resolve, reject) {
+ const timer = setTimeout(function () {
+ reject(new Error("timeout"));
+ }, ms);
+ promise.then(function (value) {
+ clearTimeout(timer);
+ resolve(value);
+ }, function (err) {
+ clearTimeout(timer);
+ reject(err);
+ });
+ });
+ }
+
+ /**
+ * Re-point a running server at the current project root WITHOUT restarting it, by sending
+ * `workspace/didChangeWorkspaceFolders` (remove the old folder, add the new one). This avoids
+ * the cold start a full restart pays on every project switch. Generic: servers that don't
+ * advertise live workspace-folder change support transparently fall back to a full restart.
+ * The open documents themselves are re-synced by DocumentSync's normal editor-change handling.
+ * @param {string} serverId
+ * @return {Promise}
+ */
+ async function changeWorkspaceRoot(serverId) {
+ const client = clients.get(serverId);
+ if (!client) {
+ return;
+ }
+ // Not up yet (e.g. the project switched before init finished) - a (re)start picks up the
+ // current root on its own.
+ if (!client.capabilities) {
+ return restartLanguageServer(serverId);
+ }
+ const wf = client.capabilities.workspace && client.capabilities.workspace.workspaceFolders;
+ // Per the LSP spec changeNotifications is `boolean | string` (a static flag or a dynamic
+ // registration id); either truthy form means the server accepts live folder changes.
+ const supportsLiveChange = !!(wf && wf.supported && wf.changeNotifications);
+ if (!supportsLiveChange) {
+ return restartLanguageServer(serverId);
+ }
+ const newVfsPath = (client.config.rootUriProvider && client.config.rootUriProvider()) || _projectRootPath();
+ const newUri = newVfsPath ? pathToServerUri(newVfsPath) : null;
+ const oldUri = client.rootUri || null;
+ if (newUri === oldUri) {
+ return; // same workspace - nothing to do
+ }
+ const conn = await getConnector();
+ const added = newUri ? [{ uri: newUri, name: FileUtils.getBaseName(newVfsPath) }] : [];
+ const removed = oldUri ? [{ uri: oldUri, name: client.rootName || FileUtils.getBaseName(oldUri) }] : [];
+ await conn.execPeer("sendNotification", {
+ serverId: serverId,
+ method: "workspace/didChangeWorkspaceFolders",
+ params: { event: { added: added, removed: removed } }
+ });
+ client.rootUri = newUri;
+ client.rootName = newVfsPath ? FileUtils.getBaseName(newVfsPath) : null;
+ // Capabilities are unchanged (no restart), but the active file is now in the new project -
+ // refresh the find-references menu state for that context.
+ FindReferencesManager.setMenuItemStateForLanguage();
+ }
+
async function restartLanguageServer(serverId) {
const client = clients.get(serverId);
if (!client) {
@@ -676,11 +755,19 @@ define(function (require, exports, module) {
client.capabilities = null;
client._completionCache = null;
DocumentSync.clearServer(client);
+ // Attempt a graceful LSP shutdown - some servers need it to flush state or clean up child
+ // processes - but BOUND it. The `shutdown` request blocks until the server replies, and a
+ // busy or cold server can be slow (or never reply), which would stall the restart; on a
+ // project switch we'd end up waiting for the old server to finish booting just to tell it to
+ // die, then cold-start a new one (a double penalty on slow CI). Give it a short budget, then
+ // hard-kill regardless. The `exit` notification expects no reply, so it stays cheap.
try {
- await conn.execPeer("sendRequest", { serverId: client.serverId, method: "shutdown", params: null });
+ await _withTimeout(
+ conn.execPeer("sendRequest", { serverId: client.serverId, method: "shutdown", params: null }),
+ SHUTDOWN_TIMEOUT_MS);
await conn.execPeer("sendNotification", { serverId: client.serverId, method: "exit", params: null });
} catch (e) {
- // Server may already be dead; fall through to a hard stop.
+ // Timed out, or the server is already dead - fall through to the hard stop.
}
await conn.execPeer("stopServer", { serverId: client.serverId });
client._stopping = false;
@@ -688,6 +775,7 @@ define(function (require, exports, module) {
exports.registerLanguageServer = registerLanguageServer;
exports.restartLanguageServer = restartLanguageServer;
+ exports.changeWorkspaceRoot = changeWorkspaceRoot;
exports.pathToServerUri = pathToServerUri;
exports.serverUriToVfsUri = serverUriToVfsUri;
});
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 55fa626468..eef1a315b9 100644
--- a/src/nls/root/strings.js
+++ b/src/nls/root/strings.js
@@ -1736,6 +1736,17 @@ define({
"OPEN_PREFERENNCES": "Open Preferences",
"FIND_ALL_REFERENCES": "Find Usages",
+ // Project-wide code intelligence (TypeScript/JavaScript LSP) - on by default
+ "CODE_INTEL_JS": "JavaScript",
+ "CODE_INTEL_TS": "TypeScript",
+ "CODE_INTEL_ENABLED_TITLE": "{0} Code Intelligence Enabled",
+ "CODE_INTEL_ENABLED_MESSAGE": "Find Usages, Rename, and Go to Definition now work across every file in this project.",
+ "CODE_INTEL_SEE_CONFIG": "See Config",
+ "CODE_INTEL_ENABLE_TS": "Enable TypeScript",
+ "CODE_INTEL_LEARN_MORE": "Learn more",
+ "CODE_INTEL_PANEL_TEXT": "Project-wide code intelligence is off. Enable it for Find Usages, Rename, and Go to Definition across every file — adds a jsconfig.json to the project root.",
+ "CODE_INTEL_PANEL_ENABLE": "Enable",
+ "CODE_INTEL_PANEL_DISMISS": "Dismiss",
"REFERENCES_IN_FILES": "references",
"REFERENCE_IN_FILES": "reference",
"REFERENCES_NO_RESULTS": "No References available for current cursor position",
diff --git a/src/styles/brackets.less b/src/styles/brackets.less
index dd788f6911..1dbd2dd20a 100644
--- a/src/styles/brackets.less
+++ b/src/styles/brackets.less
@@ -2143,6 +2143,82 @@ a, img {
}
// LSP hover documentation (rendered inside #quick-view-container)
+// Problems-panel re-enable row (TypeScriptSupport/CodeIntelligence.js).
+.ts-code-intel-panel-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px 12px;
+ border-bottom: 1px solid @bc-panel-separator;
+ background: @bc-panel-bg-promoted;
+ font-size: 13px;
+ .ts-code-intel-panel-text {
+ flex: 1;
+ color: @bc-text;
+ line-height: 1.5;
+ }
+ .ts-code-intel-panel-enable {
+ flex: none;
+ margin: 0;
+ }
+ .ts-code-intel-panel-close {
+ flex: none;
+ cursor: pointer;
+ color: @bc-text-quiet;
+ font-size: 17px;
+ line-height: 1;
+ padding: 0 2px;
+ text-decoration: none;
+ &:hover { color: @bc-text; text-decoration: none; }
+ }
+ .dark & {
+ background: @dark-bc-panel-bg-promoted;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+ .ts-code-intel-panel-text { color: @dark-bc-text; }
+ .ts-code-intel-panel-close {
+ color: #888;
+ &:hover { color: @dark-bc-text; }
+ }
+ }
+}
+
+// Project-wide code intelligence toast inner layout (TypeScriptSupport/CodeIntelligence.js).
+// The surface comes from the reusable NotificationUI `SUBTLE` style; this is just the content.
+.ts-code-intel-toast {
+ .ts-code-intel-msg {
+ font-size: 12px;
+ line-height: 1.45;
+ margin: 0;
+ }
+ .ts-code-intel-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 4px 14px;
+ margin-top: 12px;
+ }
+ // flat text actions - no chunky button chrome
+ .ts-code-intel-action {
+ background: none;
+ border: none;
+ box-shadow: none;
+ margin: 0;
+ padding: 0;
+ font-size: 12px;
+ line-height: 1.6;
+ cursor: pointer;
+ color: #0a6cbf;
+ &:hover { color: #085699; text-decoration: underline; }
+ &:focus { outline: none; box-shadow: none; }
+ }
+ .dark & {
+ .ts-code-intel-action {
+ color: #6cb6ff;
+ &:hover { color: #8cc6ff; }
+ }
+ }
+}
+
.lsp-hover-quickview {
text-align: left;
max-width: 520px;
@@ -2193,28 +2269,8 @@ a, img {
}
}
- // highlight.js token colours for the signature block. The app's global hljs theme is
- // github-dark (locked, for the always-dark sidebar); override it within the hover so the
- // signature reads well on the light theme too. The chip background follows the editor theme,
- // so we use a GitHub-light palette in light mode and a GitHub-dark palette in dark mode.
- pre code.hljs { background: none; }
- .hljs-keyword, .hljs-literal, .hljs-doctag, .hljs-meta .hljs-keyword { color: #cf222e; }
- .hljs-string, .hljs-regexp, .hljs-meta-string { color: #0a3069; }
- .hljs-title, .hljs-title.function_, .hljs-title.class_, .hljs-section { color: #6639ba; }
- .hljs-built_in, .hljs-type, .hljs-class .hljs-title { color: #953800; }
- .hljs-number, .hljs-symbol, .hljs-bullet { color: #0550ae; }
- .hljs-attr, .hljs-attribute, .hljs-property, .hljs-variable, .hljs-params { color: @bc-text-emphasized; }
- .hljs-comment, .hljs-quote, .hljs-meta { color: #6e7781; }
-
- .dark & {
- .hljs-keyword, .hljs-literal, .hljs-doctag, .hljs-meta .hljs-keyword { color: #ff7b72; }
- .hljs-string, .hljs-regexp, .hljs-meta-string { color: #a5d6ff; }
- .hljs-title, .hljs-title.function_, .hljs-title.class_, .hljs-section { color: #d2a8ff; }
- .hljs-built_in, .hljs-type, .hljs-class .hljs-title { color: #ffa657; }
- .hljs-number, .hljs-symbol, .hljs-bullet { color: #79c0ff; }
- .hljs-attr, .hljs-attribute, .hljs-property, .hljs-variable, .hljs-params { color: @dark-bc-text-emphasized; }
- .hljs-comment, .hljs-quote, .hljs-meta { color: #8b949e; }
- }
+ // hljs token colours for the signature block are shared with the code-hint doc popup -
+ // see the `.lsp-hover-quickview, .lsp-hint-doc-popup` rule below.
// Inline code: parameter names, types, identifiers.
code {
@@ -2357,6 +2413,31 @@ a, img {
}
// LSP code-hint documentation popup (shown beside the code hint list)
+// Shared highlight.js token colours for both LSP popups (hover signature + code-hint docs). The
+// app's global hljs theme is github-dark (locked, for the always-dark sidebar); these override it
+// so highlighted code reads well on the light theme too - GitHub-light palette in light mode,
+// GitHub-dark in dark mode. `&` is the popup selector, so `.dark &` scopes both correctly.
+.lsp-hover-quickview, .lsp-hint-doc-popup {
+ pre code.hljs { background: none; }
+ .hljs-keyword, .hljs-literal, .hljs-doctag, .hljs-meta .hljs-keyword { color: #cf222e; }
+ .hljs-string, .hljs-regexp, .hljs-meta-string { color: #0a3069; }
+ .hljs-title, .hljs-title.function_, .hljs-title.class_, .hljs-section { color: #6639ba; }
+ .hljs-built_in, .hljs-type, .hljs-class .hljs-title { color: #953800; }
+ .hljs-number, .hljs-symbol, .hljs-bullet { color: #0550ae; }
+ .hljs-attr, .hljs-attribute, .hljs-property, .hljs-variable, .hljs-params { color: @bc-text-emphasized; }
+ .hljs-comment, .hljs-quote, .hljs-meta { color: #6e7781; }
+
+ .dark & {
+ .hljs-keyword, .hljs-literal, .hljs-doctag, .hljs-meta .hljs-keyword { color: #ff7b72; }
+ .hljs-string, .hljs-regexp, .hljs-meta-string { color: #a5d6ff; }
+ .hljs-title, .hljs-title.function_, .hljs-title.class_, .hljs-section { color: #d2a8ff; }
+ .hljs-built_in, .hljs-type, .hljs-class .hljs-title { color: #ffa657; }
+ .hljs-number, .hljs-symbol, .hljs-bullet { color: #79c0ff; }
+ .hljs-attr, .hljs-attribute, .hljs-property, .hljs-variable, .hljs-params { color: @dark-bc-text-emphasized; }
+ .hljs-comment, .hljs-quote, .hljs-meta { color: #8b949e; }
+ }
+}
+
.lsp-hint-doc-popup {
display: none;
position: fixed;
@@ -3916,6 +3997,29 @@ label input {
cursor: pointer;
}
}
+// SUBTLE: a quiet, theme-matching surface instead of a bright colored toast.
+.notification-popup-container.style-subtle {
+ background: #ffffff;
+ color: #5a5f66;
+ border: 1px solid rgba(0, 0, 0, 0.12);
+ .notification-dialog-title {
+ color: #1c1c1c;
+ font-weight: 600;
+ font-size: 13px;
+ }
+ .notification-popup-close-button {
+ color: #9aa0a6;
+ font-weight: 400;
+ }
+ a { cursor: pointer; }
+ .dark & {
+ background: #2b2f36;
+ color: #9aa0a6;
+ border: 1px solid rgba(255, 255, 255, 0.10);
+ .notification-dialog-title { color: #eceef0; }
+ .notification-popup-close-button { color: #9aa0a6; }
+ }
+}
.notification-popup-container.animateOpen {
opacity: 1;
diff --git a/src/widgets/NotificationUI.js b/src/widgets/NotificationUI.js
index 8ab5529c3e..6592d6fcb8 100644
--- a/src/widgets/NotificationUI.js
+++ b/src/widgets/NotificationUI.js
@@ -83,7 +83,9 @@ define(function (require, exports, module) {
WARNING: "style-warning",
SUCCESS: "style-success",
ERROR: "style-error",
- DANGER: "style-danger"
+ DANGER: "style-danger",
+ // A quiet, theme-matching surface (not a bright colored toast) for low-key notifications.
+ SUBTLE: "style-subtle"
};
/**
diff --git a/test/spec/EditorCommandHandlers-integ-test.js b/test/spec/EditorCommandHandlers-integ-test.js
index 1cf50e88df..4b33190445 100644
--- a/test/spec/EditorCommandHandlers-integ-test.js
+++ b/test/spec/EditorCommandHandlers-integ-test.js
@@ -191,35 +191,44 @@ define(function (require, exports, module) {
describe("Editor Navigation Commands", function () {
it("should jump to definition", async function () {
- var promise,
- selection;
- promise = CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, {fullPath: testPath + "/test.js"});
- await awaitsForDone(promise, "Open into working set");
-
+ var selection;
+ await awaitsForDone(
+ CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, {fullPath: testPath + "/test.js"}),
+ "Open into working set");
myEditor = EditorManager.getCurrentFullEditor();
- myEditor.setCursorPos({line: 5, ch: 8});
- await awaits(1500); // for the code intelligence framework to prime up
- promise = CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION);
- await awaitsForDone(promise, "Jump To Definition");
- selection = myEditor.getSelection();
if (window.Phoenix.isNativeApp) {
- // Desktop has the LSP server (vtsls), which now provides jump-to-definition
- // for JavaScript (higher priority than the built-in Tern provider). vtsls
- // returns the full declaration range of testMe and we jump to its start
- // (the `function` keyword), giving a collapsed cursor at {0,0} - whereas Tern
- // selects the identifier name. Both correctly land on the testMe definition.
+ // Desktop has the LSP server (vtsls), which provides jump-to-definition for
+ // JavaScript at a higher priority than the built-in Tern provider. vtsls returns
+ // testMe's full declaration range and we jump to its start (the `function`
+ // keyword), giving a collapsed cursor at {0,0} - whereas Tern selects the
+ // identifier name. The server can take a moment to prime after the file opens, so
+ // retry the jump until the LSP result lands rather than using a fixed wait (which
+ // flakes on slow CI: before vtsls is ready, Tern answers with {0,9}-{0,15}).
+ await awaitsFor(async function () {
+ myEditor.setCursorPos({line: 5, ch: 8});
+ await awaitsForDone(CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION),
+ "Jump To Definition");
+ var sel = myEditor.getSelection();
+ return sel.start.line === 0 && sel.start.ch === 0 &&
+ sel.end.line === 0 && sel.end.ch === 0;
+ }, "LSP jump-to-definition to land on the testMe declaration", 30000);
+ selection = myEditor.getSelection();
expect(fixSel(selection)).toEql(fixSel({
start: {line: 0, ch: 0},
end: {line: 0, ch: 0}
}));
} else {
+ myEditor.setCursorPos({line: 5, ch: 8});
+ await awaitsForDone(CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION),
+ "Jump To Definition");
+ selection = myEditor.getSelection();
expect(fixSel(selection)).toEql(fixSel({
start: {line: 0, ch: 9},
end: {line: 0, ch: 15}
}));
}
- });
+ }, 35000);
});
if(window.Phoenix.isNativeApp) {