` element and not the parent `` element.
- elements.push({ element: element.parentElement as HTMLElement, line });
+ cachedElements.push({ element: element.parentElement as HTMLElement, line });
} else {
- elements.push({ element: element as HTMLElement, line });
+ cachedElements.push({ element: element as HTMLElement, line });
}
}
}
- return elements;
+ return cachedElements;
};
})();
@@ -51,9 +53,9 @@ const getCodeLineElements = (() => {
* If an exact match, returns a single element. If the line is between elements,
* returns the element prior to and the element after the given line.
*/
-export function getElementsForSourceLine(targetLine: number): { previous: CodeLineElement; next?: CodeLineElement; } {
+export function getElementsForSourceLine(targetLine: number, documentVersion: number): { previous: CodeLineElement; next?: CodeLineElement; } {
const lineNumber = Math.floor(targetLine);
- const lines = getCodeLineElements();
+ const lines = getCodeLineElements(documentVersion);
let previous = lines[0] || null;
for (const entry of lines) {
if (entry.line === lineNumber) {
@@ -69,8 +71,8 @@ export function getElementsForSourceLine(targetLine: number): { previous: CodeLi
/**
* Find the html elements that are at a specific pixel offset on the page.
*/
-export function getLineElementsAtPageOffset(offset: number): { previous: CodeLineElement; next?: CodeLineElement; } {
- const lines = getCodeLineElements();
+export function getLineElementsAtPageOffset(offset: number, documentVersion: number): { previous: CodeLineElement; next?: CodeLineElement; } {
+ const lines = getCodeLineElements(documentVersion);
const position = offset - window.scrollY;
let lo = -1;
let hi = lines.length - 1;
@@ -117,8 +119,8 @@ function getElementBounds({ element }: CodeLineElement): { top: number, height:
/**
* Attempt to reveal the element for a source line in the editor.
*/
-export function scrollToRevealSourceLine(line: number) {
- if (!getSettings().scrollPreviewWithEditor) {
+export function scrollToRevealSourceLine(line: number, documentVersion: number, settingsManager: SettingsManager) {
+ if (!settingsManager.settings?.scrollPreviewWithEditor) {
return;
}
@@ -127,7 +129,7 @@ export function scrollToRevealSourceLine(line: number) {
return;
}
- const { previous, next } = getElementsForSourceLine(line);
+ const { previous, next } = getElementsForSourceLine(line, documentVersion);
if (!previous) {
return;
}
@@ -147,19 +149,20 @@ export function scrollToRevealSourceLine(line: number) {
window.scroll(window.scrollX, Math.max(1, window.scrollY + scrollTo));
}
-export function getEditorLineNumberForPageOffset(offset: number) {
- const { previous, next } = getLineElementsAtPageOffset(offset);
+export function getEditorLineNumberForPageOffset(offset: number, documentVersion: number, settingsManager: SettingsManager) {
+ const lineCount = settingsManager.settings?.lineCount ?? 0;
+ const { previous, next } = getLineElementsAtPageOffset(offset, documentVersion);
if (previous) {
const previousBounds = getElementBounds(previous);
const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
if (next) {
const progressBetweenElements = offsetFromPrevious / (getElementBounds(next).top - previousBounds.top);
const line = previous.line + progressBetweenElements * (next.line - previous.line);
- return clampLine(line);
+ return clampLine(line, lineCount);
} else {
const progressWithinElement = offsetFromPrevious / (previousBounds.height);
const line = previous.line + progressWithinElement;
- return clampLine(line);
+ return clampLine(line, lineCount);
}
}
return null;
@@ -168,8 +171,8 @@ export function getEditorLineNumberForPageOffset(offset: number) {
/**
* Try to find the html element by using a fragment id
*/
-export function getLineElementForFragment(fragment: string): CodeLineElement | undefined {
- return getCodeLineElements().find((element) => {
+export function getLineElementForFragment(fragment: string, documentVersion: number): CodeLineElement | undefined {
+ return getCodeLineElements(documentVersion).find((element) => {
return element.element.id === fragment;
});
}
diff --git a/extensions/markdown-language-features/preview-src/settings.ts b/extensions/markdown-language-features/preview-src/settings.ts
index 470246f94c1..a1212fdf432 100644
--- a/extensions/markdown-language-features/preview-src/settings.ts
+++ b/extensions/markdown-language-features/preview-src/settings.ts
@@ -15,8 +15,6 @@ export interface PreviewSettings {
readonly webviewResourceRoot: string;
}
-let cachedSettings: PreviewSettings | undefined = undefined;
-
export function getData(key: string): T {
const element = document.getElementById('vscode-markdown-preview-data');
if (element) {
@@ -29,15 +27,14 @@ export function getData(key: string): T {
throw new Error(`Could not load data for ${key}`);
}
-export function getSettings(): PreviewSettings {
- if (cachedSettings) {
- return cachedSettings;
+export class SettingsManager {
+ private _settings: PreviewSettings = getData('data-settings');
+
+ public get settings(): PreviewSettings {
+ return this._settings;
}
- cachedSettings = getData('data-settings');
- if (cachedSettings) {
- return cachedSettings;
+ public updateSettings(newSettings: PreviewSettings) {
+ this._settings = newSettings;
}
-
- throw new Error('Could not load settings');
}
diff --git a/extensions/markdown-language-features/preview-src/tsconfig.json b/extensions/markdown-language-features/preview-src/tsconfig.json
index 62af34c62f8..c12ff006ed8 100644
--- a/extensions/markdown-language-features/preview-src/tsconfig.json
+++ b/extensions/markdown-language-features/preview-src/tsconfig.json
@@ -3,6 +3,7 @@
"compilerOptions": {
"outDir": "./dist/",
"jsx": "react",
+ "esModuleInterop": true,
"lib": [
"es2018",
"DOM",
diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts
index e2bd61b2e9d..f80664cd59e 100644
--- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts
+++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts
@@ -91,19 +91,21 @@ function extractDocumentLink(
}
}
-/* Used to strip brackets from the markdown link
- will be transformed to
- http://example.com
+const angleBracketLinkRe = /^<(.*)>$/;
+
+/**
+ * Used to strip brackets from the markdown link
+ *
+ * will be transformed to http://example.com
*/
export function stripAngleBrackets(link: string) {
- const bracketMatcher = /^<(.*)>$/;
- return link.replace(bracketMatcher, '$1');
+ return link.replace(angleBracketLinkRe, '$1');
}
export default class LinkProvider implements vscode.DocumentLinkProvider {
private readonly linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*(".*?")?\)/g;
private readonly referenceLinkPattern = /(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]/g;
- private readonly definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)(\S+)/gm;
+ private readonly definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^>]+>)/gm;
public provideDocumentLinks(
document: vscode.TextDocument,
@@ -192,15 +194,23 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
const pre = match[1];
const reference = match[2];
const link = match[3].trim();
-
const offset = (match.index || 0) + pre.length;
- const linkStart = document.positionAt(offset);
- const linkEnd = document.positionAt(offset + link.length);
- out.set(reference, {
- link: link,
- linkRange: new vscode.Range(linkStart, linkEnd)
- });
+ if (angleBracketLinkRe.test(link)) {
+ const linkStart = document.positionAt(offset + 1);
+ const linkEnd = document.positionAt(offset + link.length - 1);
+ out.set(reference, {
+ link: link.substring(1, link.length - 1),
+ linkRange: new vscode.Range(linkStart, linkEnd)
+ });
+ } else {
+ const linkStart = document.positionAt(offset);
+ const linkEnd = document.positionAt(offset + link.length);
+ out.set(reference, {
+ link: link,
+ linkRange: new vscode.Range(linkStart, linkEnd)
+ });
+ }
}
return out;
}
diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts
index 4d172a3306f..3009c910855 100644
--- a/extensions/markdown-language-features/src/features/preview.ts
+++ b/extensions/markdown-language-features/src/features/preview.ts
@@ -16,7 +16,7 @@ import { WebviewResourceProvider } from '../util/resources';
import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor';
import { urlToUri } from '../util/url';
import { MarkdownPreviewConfigurationManager } from './previewConfig';
-import { MarkdownContentProvider, MarkdownContentProviderOutput } from './previewContentProvider';
+import { MarkdownContentProvider } from './previewContentProvider';
const localize = nls.loadMessageBundle();
@@ -63,7 +63,7 @@ interface PreviewStyleLoadErrorMessage extends WebviewMessage {
export class PreviewDocumentVersion {
- private readonly resource: vscode.Uri;
+ public readonly resource: vscode.Uri;
private readonly version: number;
public constructor(document: vscode.TextDocument) {
@@ -314,13 +314,18 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
return;
}
+ const shouldReloadPage = !this.currentVersion || this.currentVersion.resource.toString() !== pendingVersion.resource.toString();
this.currentVersion = pendingVersion;
- const content = await this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state);
+
+ const content = await (shouldReloadPage
+ ? this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state)
+ : this._contentProvider.markdownBody(document, this));
// Another call to `doUpdate` may have happened.
// Make sure we are still updating for the correct document
if (this.currentVersion?.equals(pendingVersion)) {
- this.setContent(content);
+ this.updateWebviewContent(content.html, shouldReloadPage);
+ this.updateImageWatchers(content.containingImages);
}
}
@@ -366,7 +371,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
this._webviewPanel.webview.html = this._contentProvider.provideFileNotFoundContent(this._resource);
}
- private setContent(content: MarkdownContentProviderOutput): void {
+ private updateWebviewContent(html: string, reloadPage: boolean): void {
if (this._disposed) {
return;
}
@@ -377,9 +382,19 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
this._webviewPanel.iconPath = this.iconPath;
this._webviewPanel.webview.options = this.getWebviewOptions();
- this._webviewPanel.webview.html = content.html;
+ if (reloadPage) {
+ this._webviewPanel.webview.html = html;
+ } else {
+ this._webviewPanel.webview.postMessage({
+ type: 'updateContent',
+ content: html,
+ source: this._resource.toString(),
+ });
+ }
+ }
- const srcs = new Set(content.containingImages.map(img => img.src));
+ private updateImageWatchers(containingImages: { src: string }[]) {
+ const srcs = new Set(containingImages.map(img => img.src));
// Delete stale file watchers.
for (const [src, watcher] of [...this._fileWatchersBySrc]) {
diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts
index 5ba85bdc4d9..6d86de5351f 100644
--- a/extensions/markdown-language-features/src/features/previewContentProvider.ts
+++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts
@@ -81,7 +81,7 @@ export class MarkdownContentProvider {
const nonce = getNonce();
const csp = this.getCsp(resourceProvider, sourceUri, nonce);
- const body = await this.engine.render(markdownDocument, resourceProvider);
+ const body = await this.markdownBody(markdownDocument, resourceProvider);
const html = `
@@ -97,7 +97,6 @@ export class MarkdownContentProvider {
${body.html}
-
${this.getScripts(resourceProvider, nonce)}
`;
@@ -107,6 +106,18 @@ export class MarkdownContentProvider {
};
}
+ public async markdownBody(
+ markdownDocument: vscode.TextDocument,
+ resourceProvider: WebviewResourceProvider,
+ ): Promise {
+ const rendered = await this.engine.render(markdownDocument, resourceProvider);
+ const html = ``;
+ return {
+ html,
+ containingImages: rendered.containingImages
+ };
+ }
+
public provideFileNotFoundContent(
resource: vscode.Uri,
): string {
diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts
index abfae361cf2..29d5274dc41 100644
--- a/extensions/markdown-language-features/src/markdownEngine.ts
+++ b/extensions/markdown-language-features/src/markdownEngine.ts
@@ -112,6 +112,7 @@ export class MarkdownEngine {
this.md = (async () => {
const markdownIt = await import('markdown-it');
let md: MarkdownIt = markdownIt(await getMarkdownOptions(() => md));
+ md.linkify.set({ fuzzyLink: false });
for (const plugin of this.contributionProvider.contributions.markdownItPlugins.values()) {
try {
diff --git a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts
index fbcaf251204..4c39b57b4cb 100644
--- a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts
+++ b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts
@@ -139,11 +139,22 @@ suite('markdown.DocumentLinkProvider', () => {
}
});
- // #107471
- test('Should not consider link references starting with ^ character valid', () => {
+ test('Should not consider link references starting with ^ character valid (#107471)', () => {
const links = getLinksForFile('[^reference]: https://example.com');
assert.strictEqual(links.length, 0);
});
+
+ test('Should find definitions links with spaces in angle brackets (#136073)', () => {
+ const links = getLinksForFile([
+ '[a]: ',
+ '[b]: ',
+ ].join('\n'));
+ assert.strictEqual(links.length, 2);
+
+ const [link1, link2] = links;
+ assertRangeEqual(link1.range, new vscode.Range(0, 6, 0, 9));
+ assertRangeEqual(link2.range, new vscode.Range(1, 6, 1, 8));
+ });
});
diff --git a/extensions/markdown-language-features/src/test/inMemoryDocument.ts b/extensions/markdown-language-features/src/test/inMemoryDocument.ts
index e9629f1fbab..bb7d83907f8 100644
--- a/extensions/markdown-language-features/src/test/inMemoryDocument.ts
+++ b/extensions/markdown-language-features/src/test/inMemoryDocument.ts
@@ -16,7 +16,6 @@ export class InMemoryDocument implements vscode.TextDocument {
this._lines = this._contents.split(/\r\n|\n/g);
}
-
isUntitled: boolean = false;
languageId: string = '';
isDirty: boolean = false;
@@ -49,7 +48,7 @@ export class InMemoryDocument implements vscode.TextDocument {
const before = this._contents.slice(0, offset);
const newLines = before.match(/\r\n|\n/g);
const line = newLines ? newLines.length : 0;
- const preCharacters = before.match(/(\r\n|\n|^).*$/g);
+ const preCharacters = before.match(/(?<=\r\n|\n|^).*$/g);
return new vscode.Position(line, preCharacters ? preCharacters[0].length : 0);
}
getText(_range?: vscode.Range | undefined): string {
diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock
index 17321f36d85..1200fc4c7d5 100644
--- a/extensions/markdown-language-features/yarn.lock
+++ b/extensions/markdown-language-features/yarn.lock
@@ -107,6 +107,11 @@ mdurl@^1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+morphdom@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/morphdom/-/morphdom-2.6.1.tgz#e868e24f989fa3183004b159aed643e628b4306e"
+ integrity sha512-Y8YRbAEP3eKykroIBWrjcfMw7mmwJfjhqdpSvoqinu8Y702nAwikpXcNFDiIkyvfCLxLM9Wu95RZqo4a9jFBaA==
+
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts
index f4caacd2a15..9912c4944a6 100644
--- a/extensions/microsoft-authentication/src/AADHelper.ts
+++ b/extensions/microsoft-authentication/src/AADHelper.ts
@@ -710,7 +710,7 @@ export class AzureActiveDirectoryService {
if (this._tokens.length === 0) {
await this._keychain.deleteToken();
} else {
- this.storeTokenData();
+ await this.storeTokenData();
}
return session;
diff --git a/extensions/objective-c/cgmanifest.json b/extensions/objective-c/cgmanifest.json
index d246105f7f0..b46a4519f3f 100644
--- a/extensions/objective-c/cgmanifest.json
+++ b/extensions/objective-c/cgmanifest.json
@@ -6,7 +6,7 @@
"git": {
"name": "jeff-hykin/cpp-textmate-grammar",
"repositoryUrl": "https://github.com/jeff-hykin/cpp-textmate-grammar",
- "commitHash": "11b4b4e2abf581d0f3c6c90ac6632d1b2f974c67"
+ "commitHash": "0ef79f098ed80ce5a86be4ed40f99ebcdbac4895"
}
},
"license": "MIT",
diff --git a/extensions/objective-c/syntaxes/objective-c++.tmLanguage.json b/extensions/objective-c/syntaxes/objective-c++.tmLanguage.json
index cf91d6328b8..da1ebc1eb0e 100644
--- a/extensions/objective-c/syntaxes/objective-c++.tmLanguage.json
+++ b/extensions/objective-c/syntaxes/objective-c++.tmLanguage.json
@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
- "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/11b4b4e2abf581d0f3c6c90ac6632d1b2f974c67",
+ "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/0ef79f098ed80ce5a86be4ed40f99ebcdbac4895",
"name": "Objective-C++",
"scopeName": "source.objcpp",
"patterns": [
diff --git a/extensions/objective-c/syntaxes/objective-c.tmLanguage.json b/extensions/objective-c/syntaxes/objective-c.tmLanguage.json
index fc87a93e402..27901fd2bcd 100644
--- a/extensions/objective-c/syntaxes/objective-c.tmLanguage.json
+++ b/extensions/objective-c/syntaxes/objective-c.tmLanguage.json
@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
- "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/11b4b4e2abf581d0f3c6c90ac6632d1b2f974c67",
+ "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/0ef79f098ed80ce5a86be4ed40f99ebcdbac4895",
"name": "Objective-C",
"scopeName": "source.objc",
"patterns": [
diff --git a/extensions/package.json b/extensions/package.json
index 848eaa085f2..656a2a688d5 100644
--- a/extensions/package.json
+++ b/extensions/package.json
@@ -4,7 +4,7 @@
"license": "MIT",
"description": "Dependencies shared by all extensions",
"dependencies": {
- "typescript": "4.4.3"
+ "typescript": "^4.5.0-dev.20211029"
},
"scripts": {
"postinstall": "node ./postinstall"
diff --git a/extensions/powershell/cgmanifest.json b/extensions/powershell/cgmanifest.json
index 13eee1dbe4e..61f29281a5e 100644
--- a/extensions/powershell/cgmanifest.json
+++ b/extensions/powershell/cgmanifest.json
@@ -6,7 +6,7 @@
"git": {
"name": "PowerShell/EditorSyntax",
"repositoryUrl": "https://github.com/PowerShell/EditorSyntax",
- "commitHash": "c150c15cca30cafd2159e3f53514f93ccf4c5649"
+ "commitHash": "742f0b5d4b60f5930c0b47fcc1f646860521296e"
}
},
"license": "MIT",
diff --git a/extensions/powershell/syntaxes/powershell.tmLanguage.json b/extensions/powershell/syntaxes/powershell.tmLanguage.json
index 61267a44de1..e70dd5309d0 100644
--- a/extensions/powershell/syntaxes/powershell.tmLanguage.json
+++ b/extensions/powershell/syntaxes/powershell.tmLanguage.json
@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
- "version": "https://github.com/PowerShell/EditorSyntax/commit/c150c15cca30cafd2159e3f53514f93ccf4c5649",
+ "version": "https://github.com/PowerShell/EditorSyntax/commit/742f0b5d4b60f5930c0b47fcc1f646860521296e",
"name": "PowerShell",
"scopeName": "source.powershell",
"patterns": [
@@ -190,7 +190,7 @@
"name": "support.function.powershell"
},
{
- "match": "(?('renameShorthandProperties', true) === false ? false : preferencesConfig.get('useAliasesForRenames', true),
@@ -201,6 +186,7 @@ export default class FileConfigurationManager extends Disposable {
generateReturnInDocTemplate: config.get('suggest.jsdoc.generateReturns', true),
includeCompletionsForImportStatements: config.get('suggest.includeCompletionsForImportStatements', true),
includeCompletionsWithSnippetText: config.get('suggest.includeCompletionsWithSnippetText', true),
+ includeCompletionsWithClassMemberSnippets: config.get('suggest.includeCompletionsWithClassMemberSnippets', true),
allowIncompleteCompletions: true,
displayPartsForJSDoc: true,
...getInlayHintsPreferences(config),
diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts
index 3823e543c60..f2ede850c6a 100644
--- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts
+++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts
@@ -77,7 +77,7 @@ suite.skip('Notebook Editor', function () {
assert.strictEqual(editor2.viewColumn, vscode.ViewColumn.Two);
});
- test.skip('Opening a notebook should fire activeNotebook event changed only once', async function () {
+ test('Opening a notebook should fire activeNotebook event changed only once', async function () {
const openedEditor = utils.asPromise(vscode.window.onDidChangeActiveNotebookEditor);
const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest');
const editor = await vscode.window.showNotebookDocument(resource);
diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts
index b89ee7b3593..165773c832a 100644
--- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts
+++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts
@@ -190,6 +190,29 @@ suite('vscode API - window', () => {
}
});
+ test('editor, opening multiple at the same time #134786', async () => {
+ const fileA = await createRandomFile();
+ const fileB = await createRandomFile();
+ const fileC = await createRandomFile();
+
+ const testFiles = [fileA, fileB, fileC];
+ const result = await Promise.all(testFiles.map(async testFile => {
+ try {
+ const doc = await workspace.openTextDocument(testFile);
+ const editor = await window.showTextDocument(doc);
+
+ return editor.document.uri;
+ } catch (error) {
+ return undefined;
+ }
+ }));
+
+ assert.strictEqual(result.length, 3);
+ assert.strictEqual(result[0], undefined);
+ assert.strictEqual(result[1], undefined);
+ assert.strictEqual(result[2]?.toString(), fileC.toString());
+ });
+
test('default column when opening a file', async () => {
const [docA, docB, docC] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts
index c53015aec26..9f8da6ceb94 100644
--- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts
+++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts
@@ -363,6 +363,70 @@ import { assertNoRpc } from '../utils';
await tasksConfig.update('tasks', []);
});
});
+
+ test('Tasks can be run back to back', async () => {
+ class Pty implements Pseudoterminal {
+ writer = new EventEmitter();
+ onDidWrite = this.writer.event;
+ closer = new EventEmitter();
+ onDidClose = this.closer.event;
+
+ constructor(readonly num: number, readonly quick: boolean) { }
+
+ cleanup() {
+ this.writer.dispose();
+ this.closer.dispose();
+ }
+
+ open() {
+ this.writer.fire('starting\r\n');
+ setTimeout(() => {
+ this.closer.fire(this.num);
+ this.cleanup();
+ }, this.quick ? 1 : 200);
+ }
+
+ close() {
+ this.closer.fire(undefined);
+ this.cleanup();
+ }
+ }
+
+ async function runTask(num: number, quick: boolean) {
+ const pty = new Pty(num, quick);
+ const task = new Task(
+ { type: 'task_bug', exampleProp: `hello world ${num}` },
+ TaskScope.Workspace, `task bug ${num}`, 'task bug',
+ new CustomExecution(
+ async () => {
+ return pty;
+ },
+ ));
+ tasks.executeTask(task);
+ return new Promise(resolve => {
+ pty.onDidClose(exitCode => {
+ resolve(exitCode);
+ });
+ });
+ }
+
+
+ const [r1, r2, r3, r4] = await Promise.all([
+ runTask(1, false), runTask(2, false), runTask(3, false), runTask(4, false)
+ ]);
+ assert.strictEqual(r1, 1);
+ assert.strictEqual(r2, 2);
+ assert.strictEqual(r3, 3);
+ assert.strictEqual(r4, 4);
+
+ const [j1, j2, j3, j4] = await Promise.all([
+ runTask(5, true), runTask(6, true), runTask(7, true), runTask(8, true)
+ ]);
+ assert.strictEqual(j1, 5);
+ assert.strictEqual(j2, 6);
+ assert.strictEqual(j3, 7);
+ assert.strictEqual(j4, 8);
+ });
});
});
});
diff --git a/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json b/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json
index ba31af17cbc..0c83af45331 100644
--- a/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json
+++ b/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json
@@ -155,13 +155,13 @@
},
{
"c": "content",
- "t": "source.css.less meta.property-list.css support.type.property-name.css",
+ "t": "source.css.less meta.property-list.css entity.name.tag.css",
"r": {
- "dark_plus": "support.type.property-name: #9CDCFE",
- "light_plus": "support.type.property-name: #FF0000",
- "dark_vs": "support.type.property-name: #9CDCFE",
- "light_vs": "support.type.property-name: #FF0000",
- "hc_black": "support.type.property-name: #D4D4D4"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json b/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json
index fbe15e072a1..d78ab4d8500 100644
--- a/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json
+++ b/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json
@@ -1299,13 +1299,13 @@
},
{
"c": "font",
- "t": "source.css.scss meta.property-list.scss meta.property-name.scss support.type.property-name.css",
+ "t": "source.css.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "support.type.property-name: #9CDCFE",
- "light_plus": "support.type.property-name: #FF0000",
- "dark_vs": "support.type.property-name: #9CDCFE",
- "light_vs": "support.type.property-name: #FF0000",
- "hc_black": "support.type.property-name: #D4D4D4"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
@@ -3356,13 +3356,13 @@
},
{
"c": "font",
- "t": "source.css.scss meta.property-list.scss meta.property-name.scss support.type.property-name.css",
+ "t": "source.css.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "support.type.property-name: #9CDCFE",
- "light_plus": "support.type.property-name: #FF0000",
- "dark_vs": "support.type.property-name: #9CDCFE",
- "light_vs": "support.type.property-name: #FF0000",
- "hc_black": "support.type.property-name: #D4D4D4"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
@@ -4731,13 +4731,13 @@
},
{
"c": "content",
- "t": "source.css.scss meta.property-list.scss meta.property-name.scss support.type.property-name.css",
+ "t": "source.css.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "support.type.property-name: #9CDCFE",
- "light_plus": "support.type.property-name: #FF0000",
- "dark_vs": "support.type.property-name: #9CDCFE",
- "light_vs": "support.type.property-name: #FF0000",
- "hc_black": "support.type.property-name: #D4D4D4"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
@@ -12882,13 +12882,13 @@
},
{
"c": "font",
- "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-name.scss support.type.property-name.css",
+ "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "support.type.property-name: #9CDCFE",
- "light_plus": "support.type.property-name: #FF0000",
- "dark_vs": "support.type.property-name: #9CDCFE",
- "light_vs": "support.type.property-name: #FF0000",
- "hc_black": "support.type.property-name: #D4D4D4"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
@@ -13817,13 +13817,13 @@
},
{
"c": "style",
- "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.property-name.scss support.type.property-name.css",
+ "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "support.type.property-name: #9CDCFE",
- "light_plus": "support.type.property-name: #FF0000",
- "dark_vs": "support.type.property-name: #9CDCFE",
- "light_vs": "support.type.property-name: #FF0000",
- "hc_black": "support.type.property-name: #D4D4D4"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
@@ -16314,13 +16314,13 @@
},
{
"c": "content",
- "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-name.scss support.type.property-name.css",
+ "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "support.type.property-name: #9CDCFE",
- "light_plus": "support.type.property-name: #FF0000",
- "dark_vs": "support.type.property-name: #9CDCFE",
- "light_vs": "support.type.property-name: #FF0000",
- "hc_black": "support.type.property-name: #D4D4D4"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
@@ -18019,13 +18019,13 @@
},
{
"c": "a",
- "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.property-name.scss",
+ "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "default: #D4D4D4",
- "light_plus": "default: #000000",
- "dark_vs": "default: #D4D4D4",
- "light_vs": "default: #000000",
- "hc_black": "default: #FFFFFF"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
@@ -18140,13 +18140,13 @@
},
{
"c": "b",
- "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.property-name.scss",
+ "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss entity.name.tag.css",
"r": {
- "dark_plus": "default: #D4D4D4",
- "light_plus": "default: #000000",
- "dark_vs": "default: #D4D4D4",
- "light_vs": "default: #000000",
- "hc_black": "default: #FFFFFF"
+ "dark_plus": "entity.name.tag.css: #D7BA7D",
+ "light_plus": "entity.name.tag: #800000",
+ "dark_vs": "entity.name.tag.css: #D7BA7D",
+ "light_vs": "entity.name.tag: #800000",
+ "hc_black": "entity.name.tag.css: #D7BA7D"
}
},
{
diff --git a/extensions/yarn.lock b/extensions/yarn.lock
index 30acf6771da..98f0d057758 100644
--- a/extensions/yarn.lock
+++ b/extensions/yarn.lock
@@ -24,10 +24,10 @@ fast-plist@0.1.2:
resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8"
integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg=
-typescript@4.4.3:
- version "4.4.3"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
- integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
+typescript@^4.5.0-dev.20211029:
+ version "4.5.0-dev.20211029"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.0-dev.20211029.tgz#ec4619ab136bd70ddd9ec1a7c18783b7ce9990a3"
+ integrity sha512-N+2wLMbTq+jQmad78i4wKBGcXudBFWy+QdV1Xu9cx+F5Xi6hubBotFEzS7zA7G1Eevy6NJwlsNy0G8ok2GQ9nw==
vscode-grammar-updater@^1.0.3:
version "1.0.3"
diff --git a/package.json b/package.json
index d4bec9f273e..4b3e212937b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.62.0",
- "distro": "084dcc16aa74953bab0548580b3af2b0f2eb1134",
+ "distro": "68b7698bb44bb140dfd35df9825ea6825edf87af",
"author": {
"name": "Microsoft Corporation"
},
@@ -63,8 +63,7 @@
"@vscode/sqlite3": "4.0.12",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",
- "chokidar": "3.5.1",
- "graceful-fs": "4.2.6",
+ "graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.8",
@@ -72,7 +71,7 @@
"keytar": "7.2.0",
"minimist": "^1.2.5",
"native-is-elevated": "0.4.3",
- "native-keymap": "3.0.0",
+ "native-keymap": "3.0.1",
"native-watchdog": "1.3.0",
"node-pty": "0.11.0-beta7",
"spdlog": "^0.13.0",
@@ -84,7 +83,7 @@
"vscode-proxy-agent": "^0.11.0",
"vscode-regexpp": "^3.1.0",
"vscode-ripgrep": "^1.12.1",
- "vscode-textmate": "5.4.0",
+ "vscode-textmate": "5.4.1",
"xterm": "4.15.0-beta.10",
"xterm-addon-search": "0.9.0-beta.5",
"xterm-addon-serialize": "0.7.0-beta.2",
@@ -97,7 +96,6 @@
"devDependencies": {
"7zip": "0.0.6",
"@types/applicationinsights": "0.20.0",
- "@types/chokidar": "2.1.3",
"@types/cookie": "^0.3.3",
"@types/copy-webpack-plugin": "^6.0.3",
"@types/cssnano": "^4.0.0",
@@ -199,13 +197,13 @@
"style-loader": "^1.0.0",
"ts-loader": "^9.2.3",
"tsec": "0.1.4",
- "typescript": "^4.5.0-dev.20211021",
+ "typescript": "^4.5.0-dev.20211029",
"typescript-formatter": "7.1.0",
"underscore": "^1.12.1",
"util": "^0.12.4",
"vinyl": "^2.0.0",
"vinyl-fs": "^3.0.0",
- "vscode-debugprotocol": "1.48.0",
+ "vscode-debugprotocol": "1.50.0",
"vscode-nls-dev": "^3.3.1",
"vscode-telemetry-extractor": "^1.8.0",
"webpack": "^5.42.0",
diff --git a/remote/package.json b/remote/package.json
index a7960f23916..1851794ddd6 100644
--- a/remote/package.json
+++ b/remote/package.json
@@ -7,9 +7,8 @@
"@parcel/watcher": "2.0.0",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",
- "chokidar": "3.5.1",
"cookie": "^0.4.0",
- "graceful-fs": "4.2.6",
+ "graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.8",
@@ -24,7 +23,7 @@
"vscode-proxy-agent": "^0.11.0",
"vscode-regexpp": "^3.1.0",
"vscode-ripgrep": "^1.12.1",
- "vscode-textmate": "5.4.0",
+ "vscode-textmate": "5.4.1",
"xterm": "4.15.0-beta.10",
"xterm-addon-search": "0.9.0-beta.5",
"xterm-addon-serialize": "0.7.0-beta.2",
diff --git a/remote/web/package.json b/remote/web/package.json
index 46e5dd91a02..307a919a301 100644
--- a/remote/web/package.json
+++ b/remote/web/package.json
@@ -9,7 +9,7 @@
"jschardet": "3.0.0",
"tas-client-umd": "0.1.4",
"vscode-oniguruma": "1.5.1",
- "vscode-textmate": "5.4.0",
+ "vscode-textmate": "5.4.1",
"xterm": "4.15.0-beta.10",
"xterm-addon-search": "0.9.0-beta.5",
"xterm-addon-unicode11": "0.3.0",
diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock
index 646ff9fcd3a..384abe27126 100644
--- a/remote/web/yarn.lock
+++ b/remote/web/yarn.lock
@@ -108,10 +108,10 @@ vscode-oniguruma@1.5.1:
resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.5.1.tgz#9ca10cd3ada128bd6380344ea28844243d11f695"
integrity sha512-JrBZH8DCC262TEYcYdeyZusiETu0Vli0xFgdRwNJjDcObcRjbmJP+IFcA3ScBwIXwgFHYKbAgfxtM/Cl+3Spjw==
-vscode-textmate@5.4.0:
- version "5.4.0"
- resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.0.tgz#4b25ffc1f14ac3a90faf9a388c67a01d24257cd7"
- integrity sha512-c0Q4zYZkcLizeYJ3hNyaVUM2AA8KDhNCA3JvXY8CeZSJuBdAy3bAvSbv46RClC4P3dSO9BdwhnKEx2zOo6vP/w==
+vscode-textmate@5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.1.tgz#09d566724fc76b60b3ad9791eebf1f0b50f29e5a"
+ integrity sha512-4CvPHmfuZQaXrcCpathdh6jo7myuR+MU8BvscgQADuponpbqfmu2rwTOtCXhGwwEgStvJF8V4s9FwMKRVLNmKQ==
xterm-addon-search@0.9.0-beta.5:
version "0.9.0-beta.5"
diff --git a/remote/yarn.lock b/remote/yarn.lock
index c94c62cf7da..091d7fe7dad 100644
--- a/remote/yarn.lock
+++ b/remote/yarn.lock
@@ -127,14 +127,6 @@ agent-base@^4.3.0:
dependencies:
es6-promisify "^5.0.0"
-anymatch@~3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
- integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
- dependencies:
- normalize-path "^3.0.0"
- picomatch "^2.0.4"
-
applicationinsights@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
@@ -144,11 +136,6 @@ applicationinsights@1.0.8:
diagnostic-channel-publishers "0.2.1"
zone.js "0.7.6"
-binary-extensions@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
- integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
-
bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
@@ -156,33 +143,11 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
-braces@~3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
- integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
- dependencies:
- fill-range "^7.0.1"
-
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
-chokidar@3.5.1:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
- integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
- dependencies:
- anymatch "~3.1.1"
- braces "~3.0.2"
- glob-parent "~5.1.0"
- is-binary-path "~2.1.0"
- is-glob "~4.0.1"
- normalize-path "~3.0.0"
- readdirp "~3.5.0"
- optionalDependencies:
- fsevents "~2.3.1"
-
cookie@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
@@ -260,13 +225,6 @@ file-uri-to-path@2:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba"
integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==
-fill-range@^7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
- integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
- dependencies:
- to-regex-range "^5.0.1"
-
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -276,11 +234,6 @@ fs-extra@^8.1.0:
jsonfile "^4.0.0"
universalify "^0.1.0"
-fsevents@~2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f"
- integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==
-
ftp@^0.3.10:
version "0.3.10"
resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
@@ -301,14 +254,12 @@ get-uri@^3.0.2:
fs-extra "^8.1.0"
ftp "^0.3.10"
-glob-parent@~5.1.0:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
- integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
- dependencies:
- is-glob "^4.0.1"
+graceful-fs@4.2.8:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
+ integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
-graceful-fs@4.2.6, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
+graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
@@ -369,30 +320,6 @@ ip@^1.1.5:
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
-is-binary-path@~2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
- integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
- dependencies:
- binary-extensions "^2.0.0"
-
-is-extglob@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
- integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
-
-is-glob@^4.0.1, is-glob@~4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
- integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
- dependencies:
- is-extglob "^2.1.1"
-
-is-number@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
- integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
-
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -474,26 +401,11 @@ node-pty@0.11.0-beta7:
dependencies:
nan "^2.14.0"
-normalize-path@^3.0.0, normalize-path@~3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
- integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
-picomatch@^2.0.4:
- version "2.0.7"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
- integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
-
-picomatch@^2.2.1:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
- integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
-
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
@@ -509,13 +421,6 @@ readable-stream@1.1.x:
isarray "0.0.1"
string_decoder "~0.10.x"
-readdirp@~3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
- integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
- dependencies:
- picomatch "^2.2.1"
-
semver@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
@@ -562,13 +467,6 @@ tas-client-umd@0.1.4:
resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.4.tgz#49db4130dd63a8342fabf77185a740fc6a7bea80"
integrity sha512-1hFqJeLD3ryNikniIaO7TItlXhS5vx7bJ+wbPDf8o+IifgwwOWK2ARisdEM9SnJd0ccfcwNPG6Po+RiKn5L2hg==
-to-regex-range@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
- integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
- dependencies:
- is-number "^7.0.0"
-
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
@@ -614,10 +512,10 @@ vscode-ripgrep@^1.12.1:
https-proxy-agent "^4.0.0"
proxy-from-env "^1.1.0"
-vscode-textmate@5.4.0:
- version "5.4.0"
- resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.0.tgz#4b25ffc1f14ac3a90faf9a388c67a01d24257cd7"
- integrity sha512-c0Q4zYZkcLizeYJ3hNyaVUM2AA8KDhNCA3JvXY8CeZSJuBdAy3bAvSbv46RClC4P3dSO9BdwhnKEx2zOo6vP/w==
+vscode-textmate@5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.1.tgz#09d566724fc76b60b3ad9791eebf1f0b50f29e5a"
+ integrity sha512-4CvPHmfuZQaXrcCpathdh6jo7myuR+MU8BvscgQADuponpbqfmu2rwTOtCXhGwwEgStvJF8V4s9FwMKRVLNmKQ==
vscode-windows-ca-certs@^0.3.0:
version "0.3.0"
diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json
index 601aca6f9f0..5d97fdf7095 100644
--- a/src/tsconfig.monaco.json
+++ b/src/tsconfig.monaco.json
@@ -26,6 +26,7 @@
],
"exclude": [
"node_modules/*",
- "vs/platform/files/browser/htmlFileSystemProvider.ts"
+ "vs/platform/files/browser/htmlFileSystemProvider.ts",
+ "vs/platform/assignment/*"
]
}
diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts
index 6148eb30092..ac06cab08a9 100644
--- a/src/vs/base/browser/dom.ts
+++ b/src/vs/base/browser/dom.ts
@@ -846,6 +846,8 @@ export const EventType = {
LOAD: 'load',
BEFORE_UNLOAD: 'beforeunload',
UNLOAD: 'unload',
+ PAGE_SHOW: 'pageshow',
+ PAGE_HIDE: 'pagehide',
ABORT: 'abort',
ERROR: 'error',
RESIZE: 'resize',
diff --git a/src/vs/base/browser/indexedDB.ts b/src/vs/base/browser/indexedDB.ts
new file mode 100644
index 00000000000..7eca3955df6
--- /dev/null
+++ b/src/vs/base/browser/indexedDB.ts
@@ -0,0 +1,132 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { getErrorMessage } from 'vs/base/common/errors';
+import { mark } from 'vs/base/common/performance';
+import { isArray } from 'vs/base/common/types';
+
+class MissingStoresError extends Error {
+ constructor(readonly db: IDBDatabase) {
+ super('Missing stores');
+ }
+}
+
+export class IndexedDB {
+
+ static async create(name: string, version: number, stores: string[]): Promise {
+ const database = await IndexedDB.openDatabase(name, version, stores);
+ return new IndexedDB(database, name);
+ }
+
+ static async openDatabase(name: string, version: number, stores: string[]): Promise {
+ mark(`code/willOpenDatabase/${name}`);
+ try {
+ return await IndexedDB.doOpenDatabase(name, version, stores);
+ } catch (err) {
+ if (err instanceof MissingStoresError) {
+ console.info(`Attempting to recreate the indexedDB once.`, name);
+
+ try {
+ // Try to delete the db
+ await IndexedDB.deleteDatabase(err.db);
+ } catch (error) {
+ console.error(`Error while deleting the indexedDB`, getErrorMessage(error));
+ throw error;
+ }
+
+ return await IndexedDB.doOpenDatabase(name, version, stores);
+ }
+
+ throw err;
+ } finally {
+ mark(`code/didOpenDatabase/${name}`);
+ }
+ }
+
+ private static doOpenDatabase(name: string, version: number, stores: string[]): Promise {
+ return new Promise((c, e) => {
+ const request = window.indexedDB.open(name, version);
+ request.onerror = () => e(request.error);
+ request.onsuccess = () => {
+ const db = request.result;
+ for (const store of stores) {
+ if (!db.objectStoreNames.contains(store)) {
+ console.error(`Error while opening indexedDB. Could not find ${store} object store`);
+ e(new MissingStoresError(db));
+ return;
+ }
+ }
+ c(db);
+ };
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ for (const store of stores) {
+ if (!db.objectStoreNames.contains(store)) {
+ db.createObjectStore(store);
+ }
+ }
+ };
+ });
+ }
+
+ private static deleteDatabase(indexedDB: IDBDatabase): Promise {
+ return new Promise((c, e) => {
+ // Close any opened connections
+ indexedDB.close();
+
+ // Delete the db
+ const deleteRequest = window.indexedDB.deleteDatabase(indexedDB.name);
+ deleteRequest.onerror = (err) => e(deleteRequest.error);
+ deleteRequest.onsuccess = () => c();
+ });
+ }
+
+ private database: IDBDatabase | null = null;
+ private readonly pendingTransactions: IDBTransaction[] = [];
+
+ constructor(database: IDBDatabase, private readonly name: string) {
+ this.database = database;
+ }
+
+ getDatabase(): IDBDatabase | null {
+ return this.database;
+ }
+
+ hasPendingTransactions(): boolean {
+ return this.pendingTransactions.length > 0;
+ }
+
+ close(): void {
+ if (this.pendingTransactions.length) {
+ this.pendingTransactions.splice(0, this.pendingTransactions.length).forEach(transaction => transaction.abort());
+ }
+ if (this.database) {
+ this.database.close();
+ }
+ this.database = null;
+ }
+
+ runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest[]): Promise
+ runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest): Promise
+ async runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest | IDBRequest[]): Promise {
+ if (!this.database) {
+ throw new Error(`Database '${this.name}' is not opened.`);
+ }
+ const transaction = this.database.transaction([store], transactionMode);
+ this.pendingTransactions.push(transaction);
+ return new Promise((c, e) => {
+ transaction.oncomplete = () => {
+ if (isArray(request)) {
+ c(request.map(r => r.result));
+ } else {
+ c(request.result);
+ }
+ };
+ transaction.onerror = () => e(transaction.error);
+ const request = dbRequestFn(transaction.objectStore(store));
+ }).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1));
+ }
+
+}
diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts
index b1a0a6d988d..721059b4a27 100644
--- a/src/vs/base/browser/ui/button/button.ts
+++ b/src/vs/base/browser/ui/button/button.ts
@@ -56,8 +56,8 @@ export interface IButtonWithDescription extends IButton {
export class Button extends Disposable implements IButton {
- private _element: HTMLElement;
- private options: IButtonOptions;
+ protected _element: HTMLElement;
+ protected options: IButtonOptions;
private buttonBackground: Color | undefined;
private buttonHoverBackground: Color | undefined;
@@ -307,47 +307,15 @@ export class ButtonWithDropdown extends Disposable implements IButton {
}
}
-export class ButtonWithDescription extends Disposable implements IButtonWithDescription {
+export class ButtonWithDescription extends Button implements IButtonWithDescription {
- private _element: HTMLElement;
private _labelElement: HTMLElement;
private _descriptionElement: HTMLElement;
- private options: IButtonOptions;
-
- private buttonBackground: Color | undefined;
- private buttonHoverBackground: Color | undefined;
- private buttonForeground: Color | undefined;
- private buttonSecondaryBackground: Color | undefined;
- private buttonSecondaryHoverBackground: Color | undefined;
- private buttonSecondaryForeground: Color | undefined;
- private buttonBorder: Color | undefined;
-
- private _onDidClick = this._register(new Emitter());
- get onDidClick(): BaseEvent { return this._onDidClick.event; }
-
- private focusTracker: IFocusTracker;
constructor(container: HTMLElement, options?: IButtonOptions) {
- super();
+ super(container, options);
- this.options = options || Object.create(null);
- mixin(this.options, defaultOptions, false);
-
- this.buttonForeground = this.options.buttonForeground;
- this.buttonBackground = this.options.buttonBackground;
- this.buttonHoverBackground = this.options.buttonHoverBackground;
-
- this.buttonSecondaryForeground = this.options.buttonSecondaryForeground;
- this.buttonSecondaryBackground = this.options.buttonSecondaryBackground;
- this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground;
-
- this.buttonBorder = this.options.buttonBorder;
-
- this._element = document.createElement('a');
- this._element.classList.add('monaco-button');
this._element.classList.add('monaco-description-button');
- this._element.tabIndex = 0;
- this._element.setAttribute('role', 'button');
this._labelElement = document.createElement('div');
this._labelElement.classList.add('monaco-button-label');
@@ -358,107 +326,9 @@ export class ButtonWithDescription extends Disposable implements IButtonWithDesc
this._descriptionElement.classList.add('monaco-button-description');
this._descriptionElement.tabIndex = -1;
this._element.appendChild(this._descriptionElement);
-
- container.appendChild(this._element);
-
- this._register(Gesture.addTarget(this._element));
-
- [EventType.CLICK, TouchEventType.Tap].forEach(eventType => {
- this._register(addDisposableListener(this._element, eventType, e => {
- if (!this.enabled) {
- EventHelper.stop(e);
- return;
- }
-
- this._onDidClick.fire(e);
- }));
- });
-
- this._register(addDisposableListener(this._element, EventType.KEY_DOWN, e => {
- const event = new StandardKeyboardEvent(e);
- let eventHandled = false;
- if (this.enabled && (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) {
- this._onDidClick.fire(e);
- eventHandled = true;
- } else if (event.equals(KeyCode.Escape)) {
- this._element.blur();
- eventHandled = true;
- }
-
- if (eventHandled) {
- EventHelper.stop(event, true);
- }
- }));
-
- this._register(addDisposableListener(this._element, EventType.MOUSE_OVER, e => {
- if (!this._element.classList.contains('disabled')) {
- this.setHoverBackground();
- }
- }));
-
- this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => {
- this.applyStyles(); // restore standard styles
- }));
-
- // Also set hover background when button is focused for feedback
- this.focusTracker = this._register(trackFocus(this._element));
- this._register(this.focusTracker.onDidFocus(() => this.setHoverBackground()));
- this._register(this.focusTracker.onDidBlur(() => this.applyStyles())); // restore standard styles
-
- this.applyStyles();
}
- private setHoverBackground(): void {
- let hoverBackground;
- if (this.options.secondary) {
- hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null;
- } else {
- hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
- }
- if (hoverBackground) {
- this._element.style.backgroundColor = hoverBackground;
- }
- }
-
- style(styles: IButtonStyles): void {
- this.buttonForeground = styles.buttonForeground;
- this.buttonBackground = styles.buttonBackground;
- this.buttonHoverBackground = styles.buttonHoverBackground;
- this.buttonSecondaryForeground = styles.buttonSecondaryForeground;
- this.buttonSecondaryBackground = styles.buttonSecondaryBackground;
- this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground;
- this.buttonBorder = styles.buttonBorder;
-
- this.applyStyles();
- }
-
- private applyStyles(): void {
- if (this._element) {
- let background, foreground;
- if (this.options.secondary) {
- foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : '';
- background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : '';
- } else {
- foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
- background = this.buttonBackground ? this.buttonBackground.toString() : '';
- }
-
- const border = this.buttonBorder ? this.buttonBorder.toString() : '';
-
- this._element.style.color = foreground;
- this._element.style.backgroundColor = background;
-
- this._element.style.borderWidth = border ? '1px' : '';
- this._element.style.borderStyle = border ? 'solid' : '';
- this._element.style.borderColor = border;
- }
- }
-
- get element(): HTMLElement {
- return this._element;
- }
-
- set label(value: string) {
+ override set label(value: string) {
this._element.classList.add('monaco-text-button');
if (this.options.supportIcons) {
reset(this._labelElement, ...renderLabelWithIcons(value));
@@ -479,33 +349,6 @@ export class ButtonWithDescription extends Disposable implements IButtonWithDesc
this._descriptionElement.textContent = value;
}
}
-
- set icon(icon: CSSIcon) {
- this._element.classList.add(...CSSIcon.asClassNameArray(icon));
- }
-
- set enabled(value: boolean) {
- if (value) {
- this._element.classList.remove('disabled');
- this._element.setAttribute('aria-disabled', String(false));
- this._element.tabIndex = 0;
- } else {
- this._element.classList.add('disabled');
- this._element.setAttribute('aria-disabled', String(true));
- }
- }
-
- get enabled() {
- return !this._element.classList.contains('disabled');
- }
-
- focus(): void {
- this._element.focus();
- }
-
- hasFocus(): boolean {
- return this._element === document.activeElement;
- }
}
export class ButtonBar extends Disposable {
diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts
index 38a87147102..2ea8f0ac904 100644
--- a/src/vs/base/browser/ui/list/listWidget.ts
+++ b/src/vs/base/browser/ui/list/listWidget.ts
@@ -434,7 +434,7 @@ class TypeLabelController implements IDisposable {
.filter(() => this.automaticKeyboardNavigation || this.triggered)
.map(event => new StandardKeyboardEvent(event))
.filter(e => this.delegate.mightProducePrintableCharacter(e))
- .forEach(e => { e.stopPropagation(); e.preventDefault(); })
+ .forEach(e => e.preventDefault())
.map(event => event.browserEvent.key)
.event;
diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts
index c491aa5d20f..e1d674ff9a3 100644
--- a/src/vs/base/common/filters.ts
+++ b/src/vs/base/common/filters.ts
@@ -120,7 +120,9 @@ function isWhitespace(code: number): boolean {
}
const wordSeparators = new Set();
-'`~!@#$%^&*()-=+[{]}\\|;:\'",.<>/?'
+// These are chosen as natural word separators based on writen text.
+// It is a subset of the word separators used by the monaco editor.
+'()[]{}<>`\'"-/;:,.?!'
.split('')
.forEach(s => wordSeparators.add(s.charCodeAt(0)));
diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts
index 73b218c2d57..cd62839c133 100644
--- a/src/vs/base/common/resources.ts
+++ b/src/vs/base/common/resources.ts
@@ -276,8 +276,8 @@ export class ExtUri implements IExtUri {
return !!resource.path && resource.path[0] === '/';
}
- isEqualAuthority(a1: string, a2: string) {
- return a1 === a2 || equalsIgnoreCase(a1, a2);
+ isEqualAuthority(a1: string | undefined, a2: string | undefined) {
+ return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));
}
hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts
index 20b0af2e57f..8739130322f 100644
--- a/src/vs/base/node/id.ts
+++ b/src/vs/base/node/id.ts
@@ -93,7 +93,7 @@ export async function getMachineId(): Promise {
async function getMacMachineId(): Promise {
try {
const crypto = await import('crypto');
- const macAddress = await getMac();
+ const macAddress = getMac();
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
} catch (err) {
errors.onUnexpectedError(err);
diff --git a/src/vs/base/node/macAddress.ts b/src/vs/base/node/macAddress.ts
index 670bac70a5e..a593f6df3b6 100644
--- a/src/vs/base/node/macAddress.ts
+++ b/src/vs/base/node/macAddress.ts
@@ -16,39 +16,18 @@ function validateMacAddress(candidate: string): boolean {
return !invalidMacAddresses.has(tempCandidate);
}
-export function getMac(): Promise {
- // eslint-disable-next-line no-async-promise-executor
- return new Promise(async (resolve, reject) => {
- const timeout = setTimeout(() => reject('Unable to retrieve mac address (timeout after 10s)'), 10000);
-
- try {
- resolve(await doGetMac());
- } catch (error) {
- reject(error);
- } finally {
- clearTimeout(timeout);
- }
- });
-}
-
-function doGetMac(): Promise {
- return new Promise((resolve, reject) => {
- try {
- const ifaces = networkInterfaces();
- for (let name in ifaces) {
- const networkInterface = ifaces[name];
- if (networkInterface) {
- for (const { mac } of networkInterface) {
- if (validateMacAddress(mac)) {
- return resolve(mac);
- }
- }
+export function getMac(): string {
+ const ifaces = networkInterfaces();
+ for (let name in ifaces) {
+ const networkInterface = ifaces[name];
+ if (networkInterface) {
+ for (const { mac } of networkInterface) {
+ if (validateMacAddress(mac)) {
+ return mac;
}
}
-
- reject('Unable to retrieve mac address (unexpected format)');
- } catch (err) {
- reject(err);
}
- });
+ }
+
+ throw new Error('Unable to retrieve mac address (unexpected format)');
}
diff --git a/src/vs/base/test/browser/indexedDB.test.ts b/src/vs/base/test/browser/indexedDB.test.ts
new file mode 100644
index 00000000000..e815df717af
--- /dev/null
+++ b/src/vs/base/test/browser/indexedDB.test.ts
@@ -0,0 +1,44 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import * as assert from 'assert';
+import { IndexedDB } from 'vs/base/browser/indexedDB';
+
+suite('IndexedDB', () => {
+
+ let indexedDB: IndexedDB;
+
+ setup(async () => {
+ indexedDB = await IndexedDB.create('vscode-indexeddb-test', 1, ['test-store']);
+ await indexedDB.runInTransaction('test-store', 'readwrite', store => store.clear());
+ });
+
+ teardown(() => {
+ indexedDB.close();
+ });
+
+ test('runInTransaction', async () => {
+ await indexedDB.runInTransaction('test-store', 'readwrite', store => store.add('hello1', 'key1'));
+ const value = await indexedDB.runInTransaction('test-store', 'readonly', store => store.get('key1'));
+ assert.deepStrictEqual(value, 'hello1');
+ });
+
+ test('hasPendingTransactions', async () => {
+ const promise = indexedDB.runInTransaction('test-store', 'readwrite', store => store.add('hello2', 'key2'));
+ assert.deepStrictEqual(indexedDB.hasPendingTransactions(), true);
+ await promise;
+ assert.deepStrictEqual(indexedDB.hasPendingTransactions(), false);
+ });
+
+ test('close', async () => {
+ const promise = indexedDB.runInTransaction('test-store', 'readwrite', store => store.add('hello3', 'key3'));
+ indexedDB.close();
+ assert.deepStrictEqual(indexedDB.hasPendingTransactions(), false);
+ try {
+ await promise;
+ assert.fail('Transaction should be aborted');
+ } catch (error) { }
+ });
+
+});
diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts
index 990df495aff..0353e165cb2 100644
--- a/src/vs/base/test/common/filters.test.ts
+++ b/src/vs/base/test/common/filters.test.ts
@@ -200,6 +200,9 @@ suite('Filters', () => {
filterOk(matchesWords, 'ƶƤk', 'Ćhm: Ćlles Klar', [{ start: 0, end: 1 }, { start: 5, end: 6 }, { start: 11, end: 12 }]);
+ // Handles issue #123915
+ filterOk(matchesWords, 'C++', 'C/C++: command', [{ start: 2, end: 5 }]);
+
// assert.ok(matchesWords('gipu', 'Category: Git: Pull', true) === null);
// assert.deepStrictEqual(matchesWords('pu', 'Category: Git: Pull', true), [{ start: 15, end: 17 }]);
@@ -215,7 +218,6 @@ suite('Filters', () => {
filterOk(matchesWords, 'foo bar', 'foo-bar');
filterOk(matchesWords, 'foo bar', '123 foo-bar 456');
- filterOk(matchesWords, 'foo+bar', 'foo-bar');
filterOk(matchesWords, 'foo-bar', 'foo bar');
filterOk(matchesWords, 'foo:bar', 'foo:bar');
});
diff --git a/src/vs/base/test/node/id.test.ts b/src/vs/base/test/node/id.test.ts
index 4d12416329b..2ef49a3d4ba 100644
--- a/src/vs/base/test/node/id.test.ts
+++ b/src/vs/base/test/node/id.test.ts
@@ -16,7 +16,7 @@ flakySuite('ID', () => {
});
test('getMac', async () => {
- const macAddress = await getMac();
+ const macAddress = getMac();
assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(macAddress), `Expected a MAC address, got: ${macAddress}`);
});
});
diff --git a/src/vs/base/test/node/processes/processes.test.ts b/src/vs/base/test/node/processes/processes.integrationTest.ts
similarity index 100%
rename from src/vs/base/test/node/processes/processes.test.ts
rename to src/vs/base/test/node/processes/processes.integrationTest.ts
diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
index e8c22a0c75c..f958b760a80 100644
--- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
+++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
@@ -95,6 +95,7 @@ import { SharedProcessTunnelService } from 'vs/platform/remote/node/sharedProces
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService';
import { IUserConfigurationFileService, UserConfigurationFileServiceId } from 'vs/platform/configuration/common/userConfigurationFileService';
+import { AssignmentService } from 'vs/platform/assignment/common/assignmentService';
class SharedProcessMain extends Disposable {
@@ -240,6 +241,9 @@ class SharedProcessMain extends Disposable {
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(this.server.getChannel('extensionRecommendationNotification', activeWindowRouter)));
+ // Assignment Service (Experiment service w/out scorecards)
+ const assignmentService = new AssignmentService(this.configuration.machineId, configurationService, productService);
+
// Telemetry
let telemetryService: ITelemetryService;
const appenders: ITelemetryAppender[] = [];
@@ -250,7 +254,16 @@ class SharedProcessMain extends Disposable {
// Application Insights
if (productService.aiConfig && productService.aiConfig.asimovKey) {
- const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey);
+ const testCollector = await assignmentService.getTreatment('telemetryMigration') ?? false;
+ const insiders = productService.quality !== 'stable';
+ // Insiders send to both collector and vortex if assigned.
+ // Stable only send to one
+ if (insiders && testCollector) {
+ const collectorAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, testCollector, true);
+ this._register(toDisposable(() => collectorAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
+ appenders.push(collectorAppender);
+ }
+ const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, insiders ? false : testCollector);
this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
appenders.push(appInsightsAppender);
}
diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts
index cea03505221..323517cac2d 100644
--- a/src/vs/editor/browser/controller/textAreaHandler.ts
+++ b/src/vs/editor/browser/controller/textAreaHandler.ts
@@ -578,6 +578,9 @@ export class TextAreaHandler extends ViewPart {
top, left,
canUseZeroSizeTextarea ? 0 : 1, this._lineHeight
);
+ // In case the textarea contains a word, we're going to try to align the textarea's cursor
+ // with our cursor by scrolling the textarea as much as possible
+ this.textArea.domNode.scrollLeft = 1000000;
return;
}
diff --git a/src/vs/editor/common/commands/replaceCommand.ts b/src/vs/editor/common/commands/replaceCommand.ts
index 3e628f2db76..4d036be228c 100644
--- a/src/vs/editor/common/commands/replaceCommand.ts
+++ b/src/vs/editor/common/commands/replaceCommand.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Range } from 'vs/editor/common/core/range';
-import { Selection } from 'vs/editor/common/core/selection';
+import { Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
@@ -27,12 +27,7 @@ export class ReplaceCommand implements ICommand {
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
let inverseEditOperations = helper.getInverseEditOperations();
let srcRange = inverseEditOperations[0].range;
- return new Selection(
- srcRange.endLineNumber,
- srcRange.endColumn,
- srcRange.endLineNumber,
- srcRange.endColumn
- );
+ return Selection.fromPositions(srcRange.getEndPosition());
}
}
@@ -53,7 +48,7 @@ export class ReplaceCommandThatSelectsText implements ICommand {
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
const srcRange = inverseEditOperations[0].range;
- return new Selection(srcRange.startLineNumber, srcRange.startColumn, srcRange.endLineNumber, srcRange.endColumn);
+ return Selection.fromRange(srcRange, SelectionDirection.LTR);
}
}
@@ -76,12 +71,7 @@ export class ReplaceCommandWithoutChangingPosition implements ICommand {
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
let inverseEditOperations = helper.getInverseEditOperations();
let srcRange = inverseEditOperations[0].range;
- return new Selection(
- srcRange.startLineNumber,
- srcRange.startColumn,
- srcRange.startLineNumber,
- srcRange.startColumn
- );
+ return Selection.fromPositions(srcRange.getStartPosition());
}
}
@@ -108,12 +98,7 @@ export class ReplaceCommandWithOffsetCursorState implements ICommand {
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
let inverseEditOperations = helper.getInverseEditOperations();
let srcRange = inverseEditOperations[0].range;
- return new Selection(
- srcRange.endLineNumber + this._lineNumberDeltaOffset,
- srcRange.endColumn + this._columnDeltaOffset,
- srcRange.endLineNumber + this._lineNumberDeltaOffset,
- srcRange.endColumn + this._columnDeltaOffset
- );
+ return Selection.fromPositions(srcRange.getEndPosition().delta(this._lineNumberDeltaOffset, this._columnDeltaOffset));
}
}
diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts
index f1d27757e25..b2ce0c7282b 100644
--- a/src/vs/editor/common/controller/cursorCommon.ts
+++ b/src/vs/editor/common/controller/cursorCommon.ts
@@ -287,31 +287,11 @@ export class SingleCursorState {
}
private static _computeSelection(selectionStart: Range, position: Position): Selection {
- let startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number;
- if (selectionStart.isEmpty()) {
- startLineNumber = selectionStart.startLineNumber;
- startColumn = selectionStart.startColumn;
- endLineNumber = position.lineNumber;
- endColumn = position.column;
+ if (selectionStart.isEmpty() || !position.isBeforeOrEqual(selectionStart.getStartPosition())) {
+ return Selection.fromPositions(selectionStart.getStartPosition(), position);
} else {
- if (position.isBeforeOrEqual(selectionStart.getStartPosition())) {
- startLineNumber = selectionStart.endLineNumber;
- startColumn = selectionStart.endColumn;
- endLineNumber = position.lineNumber;
- endColumn = position.column;
- } else {
- startLineNumber = selectionStart.startLineNumber;
- startColumn = selectionStart.startColumn;
- endLineNumber = position.lineNumber;
- endColumn = position.column;
- }
+ return Selection.fromPositions(selectionStart.getEndPosition(), position);
}
- return new Selection(
- startLineNumber,
- startColumn,
- endLineNumber,
- endColumn
- );
}
}
@@ -365,13 +345,11 @@ export class CursorState {
}
public static fromModelSelection(modelSelection: ISelection): PartialModelCursorState {
- const selectionStartLineNumber = modelSelection.selectionStartLineNumber;
- const selectionStartColumn = modelSelection.selectionStartColumn;
- const positionLineNumber = modelSelection.positionLineNumber;
- const positionColumn = modelSelection.positionColumn;
+ const selection = Selection.liftSelection(modelSelection);
const modelState = new SingleCursorState(
- new Range(selectionStartLineNumber, selectionStartColumn, selectionStartLineNumber, selectionStartColumn), 0,
- new Position(positionLineNumber, positionColumn), 0
+ Range.fromPositions(selection.getSelectionStart()),
+ 0,
+ selection.getPosition(), 0
);
return CursorState.fromModelState(modelState);
}
diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts
index 202fb6bc0c8..09dbf3671cf 100644
--- a/src/vs/editor/common/controller/cursorTypeOperations.ts
+++ b/src/vs/editor/common/controller/cursorTypeOperations.ts
@@ -739,7 +739,7 @@ export class TypeOperations {
if (electricAction.matchOpenBracket) {
let endColumn = (lineTokens.getLineContent() + ch).lastIndexOf(electricAction.matchOpenBracket) + 1;
- let match = model.findMatchingBracketUp(electricAction.matchOpenBracket, {
+ let match = model.bracketPairs.findMatchingBracketUp(electricAction.matchOpenBracket, {
lineNumber: position.lineNumber,
column: endColumn
});
diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts
index bb07a4da717..8335e7abfec 100644
--- a/src/vs/editor/common/controller/oneCursor.ts
+++ b/src/vs/editor/common/controller/oneCursor.ts
@@ -6,7 +6,7 @@
import { CursorContext, CursorState, ICursorSimpleModel, SingleCursorState } from 'vs/editor/common/controller/cursorCommon';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
-import { Selection, SelectionDirection } from 'vs/editor/common/core/selection';
+import { Selection } from 'vs/editor/common/core/selection';
import { PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model';
/**
@@ -63,10 +63,7 @@ export class Cursor {
public readSelectionFromMarkers(context: CursorContext): Selection {
const range = context.model._getTrackedRange(this._selTrackedRange!)!;
- if (this.modelState.selection.getDirection() === SelectionDirection.LTR) {
- return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
- }
- return new Selection(range.endLineNumber, range.endColumn, range.startLineNumber, range.startColumn);
+ return Selection.fromRange(range, this.modelState.selection.getDirection());
}
public ensureValidState(context: CursorContext): void {
diff --git a/src/vs/editor/common/core/selection.ts b/src/vs/editor/common/core/selection.ts
index 3ee2e70af29..1e0db95c8f2 100644
--- a/src/vs/editor/common/core/selection.ts
+++ b/src/vs/editor/common/core/selection.ts
@@ -128,6 +128,13 @@ export class Selection extends Range {
return new Position(this.positionLineNumber, this.positionColumn);
}
+ /**
+ * Get the position at the start of the selection.
+ */
+ public getSelectionStart(): Position {
+ return new Position(this.selectionStartLineNumber, this.selectionStartColumn);
+ }
+
/**
* Create a new selection with a different `selectionStartLineNumber` and `selectionStartColumn`.
*/
@@ -147,6 +154,17 @@ export class Selection extends Range {
return new Selection(start.lineNumber, start.column, end.lineNumber, end.column);
}
+ /**
+ * Creates a `Selection` from a range, given a direction.
+ */
+ public static fromRange(range: Range, direction: SelectionDirection): Selection {
+ if (direction === SelectionDirection.LTR) {
+ return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
+ } else {
+ return new Selection(range.endLineNumber, range.endColumn, range.startLineNumber, range.startColumn);
+ }
+ }
+
/**
* Create a `Selection` from an `ISelection`.
*/
diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts
index 0f12fbe999a..7266715e1f3 100644
--- a/src/vs/editor/common/model.ts
+++ b/src/vs/editor/common/model.ts
@@ -529,16 +529,6 @@ export class FindMatch {
}
}
-/**
- * @internal
- */
-export interface IFoundBracket {
- range: Range;
- open: string[];
- close: string[];
- isOpen: boolean;
-}
-
/**
* Describes the behavior of decorations when typing/editing near their edges.
* Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior`
@@ -968,46 +958,6 @@ export interface ITextModel {
*/
getWordUntilPosition(position: IPosition): IWordAtPosition;
- /**
- * Find the matching bracket of `request` up, counting brackets.
- * @param request The bracket we're searching for
- * @param position The position at which to start the search.
- * @return The range of the matching bracket, or null if the bracket match was not found.
- * @internal
- */
- findMatchingBracketUp(bracket: string, position: IPosition): Range | null;
-
- /**
- * Find the first bracket in the model before `position`.
- * @param position The position at which to start the search.
- * @return The info for the first bracket before `position`, or null if there are no more brackets before `positions`.
- * @internal
- */
- findPrevBracket(position: IPosition): IFoundBracket | null;
-
- /**
- * Find the first bracket in the model after `position`.
- * @param position The position at which to start the search.
- * @return The info for the first bracket after `position`, or null if there are no more brackets after `positions`.
- * @internal
- */
- findNextBracket(position: IPosition): IFoundBracket | null;
-
- /**
- * Find the enclosing brackets that contain `position`.
- * @param position The position at which to start the search.
- * @internal
- */
- findEnclosingBrackets(position: IPosition, maxDuration?: number): [Range, Range] | null;
-
- /**
- * Given a `position`, if the position is on top or near a bracket,
- * find the matching bracket of that bracket and return the ranges of both brackets.
- * @param position The position at which to look for a bracket.
- * @internal
- */
- matchBracket(position: IPosition): [Range, Range] | null;
-
/**
* @internal
*/
diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairs.ts b/src/vs/editor/common/model/bracketPairs/bracketPairs.ts
index d9dfc530aaf..75f5b9a3b6b 100644
--- a/src/vs/editor/common/model/bracketPairs/bracketPairs.ts
+++ b/src/vs/editor/common/model/bracketPairs/bracketPairs.ts
@@ -4,24 +4,70 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
-import { Range } from 'vs/editor/common/core/range';
+import { IPosition } from 'vs/editor/common/core/position';
+import { IRange, Range } from 'vs/editor/common/core/range';
export interface IBracketPairs {
/**
- * Gets all bracket pairs that intersect the given position.
- * The result is sorted by the start position.
- */
- getBracketPairsInRange(range: Range): BracketPairInfo[];
+ * Is fired when bracket pairs change, either due to a text or a settings change.
+ */
+ onDidChange: Event;
/**
* Gets all bracket pairs that intersect the given position.
* The result is sorted by the start position.
*/
- getBracketPairsInRangeWithMinIndentation(range: Range): BracketPairWithMinIndentationInfo[];
+ getBracketPairsInRange(range: IRange): BracketPairInfo[];
- getBracketsInRange(range: Range): BracketInfo[];
+ /**
+ * Gets all bracket pairs that intersect the given position.
+ * The result is sorted by the start position.
+ */
+ getBracketPairsInRangeWithMinIndentation(range: IRange): BracketPairWithMinIndentationInfo[];
- onDidChange: Event;
+ getBracketsInRange(range: IRange): BracketInfo[];
+
+ /**
+ * Find the matching bracket of `request` up, counting brackets.
+ * @param request The bracket we're searching for
+ * @param position The position at which to start the search.
+ * @return The range of the matching bracket, or null if the bracket match was not found.
+ */
+ findMatchingBracketUp(bracket: string, position: IPosition): Range | null;
+
+ /**
+ * Find the first bracket in the model before `position`.
+ * @param position The position at which to start the search.
+ * @return The info for the first bracket before `position`, or null if there are no more brackets before `positions`.
+ */
+ findPrevBracket(position: IPosition): IFoundBracket | null;
+
+ /**
+ * Find the first bracket in the model after `position`.
+ * @param position The position at which to start the search.
+ * @return The info for the first bracket after `position`, or null if there are no more brackets after `positions`.
+ */
+ findNextBracket(position: IPosition): IFoundBracket | null;
+
+ /**
+ * Find the enclosing brackets that contain `position`.
+ * @param position The position at which to start the search.
+ */
+ findEnclosingBrackets(position: IPosition, maxDuration?: number): [Range, Range] | null;
+
+ /**
+ * Given a `position`, if the position is on top or near a bracket,
+ * find the matching bracket of that bracket and return the ranges of both brackets.
+ * @param position The position at which to look for a bracket.
+ */
+ matchBracket(position: IPosition): [Range, Range] | null;
+}
+
+export interface IFoundBracket {
+ range: Range;
+ open: string[];
+ close: string[];
+ isOpen: boolean;
}
export class BracketInfo {
diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsImpl.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsImpl.ts
index 5d583296185..7fcf3855a54 100644
--- a/src/vs/editor/common/model/bracketPairs/bracketPairsImpl.ts
+++ b/src/vs/editor/common/model/bracketPairs/bracketPairsImpl.ts
@@ -5,105 +5,737 @@
import { Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle';
+import { LineTokens } from 'vs/editor/common/core/lineTokens';
+import { IPosition, Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
-import { ITextModel } from 'vs/editor/common/model';
-import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairs } from 'vs/editor/common/model/bracketPairs/bracketPairs';
-import { BackgroundTokenizationState, TextModel } from 'vs/editor/common/model/textModel';
+import { BracketPairsTree } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree';
+import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairs, IFoundBracket } from 'vs/editor/common/model/bracketPairs/bracketPairs';
+import { TextModel } from 'vs/editor/common/model/textModel';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
-import { ILanguageConfigurationService, ResolvedLanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry';
-import { AstNode, AstNodeKind } from './impl/ast';
-import { TextEditInfo } from './impl/beforeEditPositionMapper';
-import { LanguageAgnosticBracketTokens } from './impl/brackets';
-import { Length, lengthAdd, lengthGreaterThanEqual, lengthLessThanEqual, lengthOfString, lengthsToRange, lengthZero, positionToLength, toLength } from './impl/length';
-import { parseDocument } from './impl/parser';
-import { DenseKeyProvider } from './impl/smallImmutableSet';
-import { FastTokenizer, TextBufferTokenizer } from './impl/tokenizer';
+import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry';
+import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports';
+import { RichEditBrackets, BracketsUtils, RichEditBracket } from 'vs/editor/common/modes/supports/richEditBrackets';
export class BracketPairs extends Disposable implements IBracketPairs {
- private readonly cache = this._register(new MutableDisposable>());
+ private readonly bracketPairsTree = this._register(new MutableDisposable>());
private readonly onDidChangeEmitter = new Emitter();
public readonly onDidChange = this.onDidChangeEmitter.event;
- get isDocumentSupported() {
+ private get isDocumentSupported() {
const maxSupportedDocumentLength = /* max lines */ 50_000 * /* average column count */ 100;
return this.textModel.getValueLength() <= maxSupportedDocumentLength;
}
private bracketsRequested = false;
- constructor(
+ public constructor(
private readonly textModel: TextModel,
private readonly languageConfigurationService: ILanguageConfigurationService
) {
super();
this._register(textModel.onDidChangeOptions(e => {
- this.cache.clear();
- this.updateCache();
+ this.bracketPairsTree.clear();
+ this.updateBracketPairsTree();
}));
this._register(textModel.onDidChangeLanguage(e => {
- this.cache.clear();
- this.updateCache();
+ this.bracketPairsTree.clear();
+ this.updateBracketPairsTree();
}));
this._register(
this.languageConfigurationService.onDidChange(e => {
- if (!e.languageId || this.cache.value?.object.didLanguageChange(e.languageId)) {
- this.cache.clear();
- this.updateCache();
+ if (!e.languageId || this.bracketPairsTree.value?.object.didLanguageChange(e.languageId)) {
+ this.bracketPairsTree.clear();
+ this.updateBracketPairsTree();
}
})
);
}
- private updateCache() {
+ private updateBracketPairsTree() {
if (this.bracketsRequested && this.isDocumentSupported) {
- if (!this.cache.value) {
+ if (!this.bracketPairsTree.value) {
const store = new DisposableStore();
- this.cache.value = createDisposableRef(
+ this.bracketPairsTree.value = createDisposableRef(
store.add(
- new ActiveBracketPairsImpl(this.textModel, (languageId) => {
+ new BracketPairsTree(this.textModel, (languageId) => {
return this.languageConfigurationService.getLanguageConfiguration(languageId);
})
),
store
);
- store.add(this.cache.value.object.onDidChange(e => this.onDidChangeEmitter.fire(e)));
+ store.add(this.bracketPairsTree.value.object.onDidChange(e => this.onDidChangeEmitter.fire(e)));
this.onDidChangeEmitter.fire();
}
} else {
- this.cache.clear();
+ this.bracketPairsTree.clear();
this.onDidChangeEmitter.fire();
}
}
- handleContentChanged(change: IModelContentChangedEvent) {
- this.cache.value?.object.handleContentChanged(change);
+ public handleContentChanged(change: IModelContentChangedEvent) {
+ this.bracketPairsTree.value?.object.handleContentChanged(change);
}
/**
* Returns all bracket pairs that intersect the given range.
* The result is sorted by the start position.
*/
- getBracketPairsInRange(range: Range): BracketPairInfo[] {
+ public getBracketPairsInRange(range: Range): BracketPairInfo[] {
this.bracketsRequested = true;
- this.updateCache();
- return this.cache.value?.object.getBracketPairsInRange(range, false) || [];
+ this.updateBracketPairsTree();
+ return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, false) || [];
}
- getBracketPairsInRangeWithMinIndentation(range: Range): BracketPairWithMinIndentationInfo[] {
+ public getBracketPairsInRangeWithMinIndentation(range: Range): BracketPairWithMinIndentationInfo[] {
this.bracketsRequested = true;
- this.updateCache();
- return this.cache.value?.object.getBracketPairsInRange(range, true) || [];
+ this.updateBracketPairsTree();
+ return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, true) || [];
}
- getBracketsInRange(range: Range): BracketInfo[] {
+ public getBracketsInRange(range: Range): BracketInfo[] {
this.bracketsRequested = true;
- this.updateCache();
- return this.cache.value?.object.getBracketsInRange(range) || [];
+ this.updateBracketPairsTree();
+ return this.bracketPairsTree.value?.object.getBracketsInRange(range) || [];
+ }
+
+ public findMatchingBracketUp(_bracket: string, _position: IPosition): Range | null {
+ let bracket = _bracket.toLowerCase();
+ let position = this.textModel.validatePosition(_position);
+
+ const languageId = this.textModel.getLanguageIdAtPosition(position.lineNumber, position.column);
+ let bracketsSupport = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
+
+ if (!bracketsSupport) {
+ return null;
+ }
+
+ let data = bracketsSupport.textIsBracket[bracket];
+
+ if (!data) {
+ return null;
+ }
+
+ return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, null));
+ }
+
+ public matchBracket(position: IPosition): [Range, Range] | null {
+ return this._matchBracket(this.textModel.validatePosition(position));
+ }
+
+ private _establishBracketSearchOffsets(position: Position, lineTokens: LineTokens, modeBrackets: RichEditBrackets, tokenIndex: number) {
+ const tokenCount = lineTokens.getCount();
+ const currentLanguageId = lineTokens.getLanguageId(tokenIndex);
+
+ // limit search to not go before `maxBracketLength`
+ let searchStartOffset = Math.max(0, position.column - 1 - modeBrackets.maxBracketLength);
+ for (let i = tokenIndex - 1; i >= 0; i--) {
+ const tokenEndOffset = lineTokens.getEndOffset(i);
+ if (tokenEndOffset <= searchStartOffset) {
+ break;
+ }
+ if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
+ searchStartOffset = tokenEndOffset;
+ break;
+ }
+ }
+
+ // limit search to not go after `maxBracketLength`
+ let searchEndOffset = Math.min(lineTokens.getLineContent().length, position.column - 1 + modeBrackets.maxBracketLength);
+ for (let i = tokenIndex + 1; i < tokenCount; i++) {
+ const tokenStartOffset = lineTokens.getStartOffset(i);
+ if (tokenStartOffset >= searchEndOffset) {
+ break;
+ }
+ if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
+ searchEndOffset = tokenStartOffset;
+ break;
+ }
+ }
+
+ return { searchStartOffset, searchEndOffset };
+ }
+
+ private _matchBracket(position: Position): [Range, Range] | null {
+ const lineNumber = position.lineNumber;
+ const lineTokens = this.textModel.getLineTokens(lineNumber);
+ const lineText = this.textModel.getLineContent(lineNumber);
+
+ const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
+ if (tokenIndex < 0) {
+ return null;
+ }
+ const currentModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex)).brackets;
+
+ // check that the token is not to be ignored
+ if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) {
+
+ let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, currentModeBrackets, tokenIndex);
+
+ // it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
+ // `bestResult` will contain the most right-side result
+ let bestResult: [Range, Range] | null = null;
+ while (true) {
+ const foundBracket = BracketsUtils.findNextBracketInRange(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (!foundBracket) {
+ // there are no more brackets in this text
+ break;
+ }
+
+ // check that we didn't hit a bracket too far away from position
+ if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
+ const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
+ const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], null);
+ if (r) {
+ if (r instanceof BracketSearchCanceled) {
+ return null;
+ }
+ bestResult = r;
+ }
+ }
+
+ searchStartOffset = foundBracket.endColumn - 1;
+ }
+
+ if (bestResult) {
+ return bestResult;
+ }
+ }
+
+ // If position is in between two tokens, try also looking in the previous token
+ if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) {
+ const prevTokenIndex = tokenIndex - 1;
+ const prevModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(prevTokenIndex)).brackets;
+
+ // check that previous token is not to be ignored
+ if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) {
+
+ let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, prevModeBrackets, prevTokenIndex);
+
+ const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+
+ // check that we didn't hit a bracket too far away from position
+ if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
+ const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
+ const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], null);
+ if (r) {
+ if (r instanceof BracketSearchCanceled) {
+ return null;
+ }
+ return r;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null | BracketSearchCanceled {
+ if (!data) {
+ return null;
+ }
+
+ const matched = (
+ isOpen
+ ? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate)
+ : this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate)
+ );
+
+ if (!matched) {
+ return null;
+ }
+
+ if (matched instanceof BracketSearchCanceled) {
+ return matched;
+ }
+
+ return [foundBracket, matched];
+ }
+
+ private _findMatchingBracketUp(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
+ // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
+
+ const languageId = bracket.languageId;
+ const reversedBracketRegex = bracket.reversedRegex;
+ let count = -1;
+
+ let totalCallCount = 0;
+ const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
+ while (true) {
+ if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
+ return BracketSearchCanceled.INSTANCE;
+ }
+ const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (!r) {
+ break;
+ }
+
+ const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
+ if (bracket.isOpen(hitText)) {
+ count++;
+ } else if (bracket.isClose(hitText)) {
+ count--;
+ }
+
+ if (count === 0) {
+ return r;
+ }
+
+ searchEndOffset = r.startColumn - 1;
+ }
+
+ return null;
+ };
+
+ for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {
+ const lineTokens = this.textModel.getLineTokens(lineNumber);
+ const tokenCount = lineTokens.getCount();
+ const lineText = this.textModel.getLineContent(lineNumber);
+
+ let tokenIndex = tokenCount - 1;
+ let searchStartOffset = lineText.length;
+ let searchEndOffset = lineText.length;
+ if (lineNumber === position.lineNumber) {
+ tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
+ searchStartOffset = position.column - 1;
+ searchEndOffset = position.column - 1;
+ }
+
+ let prevSearchInToken = true;
+ for (; tokenIndex >= 0; tokenIndex--) {
+ const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
+
+ if (searchInToken) {
+ // this token should be searched
+ if (prevSearchInToken) {
+ // the previous token should be searched, simply extend searchStartOffset
+ searchStartOffset = lineTokens.getStartOffset(tokenIndex);
+ } else {
+ // the previous token should not be searched
+ searchStartOffset = lineTokens.getStartOffset(tokenIndex);
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ }
+ } else {
+ // this token should not be searched
+ if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return r;
+ }
+ }
+ }
+
+ prevSearchInToken = searchInToken;
+ }
+
+ if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return r;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private _findMatchingBracketDown(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
+ // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
+
+ const languageId = bracket.languageId;
+ const bracketRegex = bracket.forwardRegex;
+ let count = 1;
+
+ let totalCallCount = 0;
+ const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
+ while (true) {
+ if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
+ return BracketSearchCanceled.INSTANCE;
+ }
+ const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (!r) {
+ break;
+ }
+
+ const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
+ if (bracket.isOpen(hitText)) {
+ count++;
+ } else if (bracket.isClose(hitText)) {
+ count--;
+ }
+
+ if (count === 0) {
+ return r;
+ }
+
+ searchStartOffset = r.endColumn - 1;
+ }
+
+ return null;
+ };
+
+ const lineCount = this.textModel.getLineCount();
+ for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
+ const lineTokens = this.textModel.getLineTokens(lineNumber);
+ const tokenCount = lineTokens.getCount();
+ const lineText = this.textModel.getLineContent(lineNumber);
+
+ let tokenIndex = 0;
+ let searchStartOffset = 0;
+ let searchEndOffset = 0;
+ if (lineNumber === position.lineNumber) {
+ tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
+ searchStartOffset = position.column - 1;
+ searchEndOffset = position.column - 1;
+ }
+
+ let prevSearchInToken = true;
+ for (; tokenIndex < tokenCount; tokenIndex++) {
+ const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
+
+ if (searchInToken) {
+ // this token should be searched
+ if (prevSearchInToken) {
+ // the previous token should be searched, simply extend searchEndOffset
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ } else {
+ // the previous token should not be searched
+ searchStartOffset = lineTokens.getStartOffset(tokenIndex);
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ }
+ } else {
+ // this token should not be searched
+ if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return r;
+ }
+ }
+ }
+
+ prevSearchInToken = searchInToken;
+ }
+
+ if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return r;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public findPrevBracket(_position: IPosition): IFoundBracket | null {
+ const position = this.textModel.validatePosition(_position);
+
+ let languageId: string | null = null;
+ let modeBrackets: RichEditBrackets | null = null;
+ for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {
+ const lineTokens = this.textModel.getLineTokens(lineNumber);
+ const tokenCount = lineTokens.getCount();
+ const lineText = this.textModel.getLineContent(lineNumber);
+
+ let tokenIndex = tokenCount - 1;
+ let searchStartOffset = lineText.length;
+ let searchEndOffset = lineText.length;
+ if (lineNumber === position.lineNumber) {
+ tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
+ searchStartOffset = position.column - 1;
+ searchEndOffset = position.column - 1;
+ const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
+ if (languageId !== tokenLanguageId) {
+ languageId = tokenLanguageId;
+ modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
+ }
+ }
+
+ let prevSearchInToken = true;
+ for (; tokenIndex >= 0; tokenIndex--) {
+ const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
+
+ if (languageId !== tokenLanguageId) {
+ // language id change!
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return this._toFoundBracket(modeBrackets, r);
+ }
+ prevSearchInToken = false;
+ }
+ languageId = tokenLanguageId;
+ modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
+ }
+
+ const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
+
+ if (searchInToken) {
+ // this token should be searched
+ if (prevSearchInToken) {
+ // the previous token should be searched, simply extend searchStartOffset
+ searchStartOffset = lineTokens.getStartOffset(tokenIndex);
+ } else {
+ // the previous token should not be searched
+ searchStartOffset = lineTokens.getStartOffset(tokenIndex);
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ }
+ } else {
+ // this token should not be searched
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return this._toFoundBracket(modeBrackets, r);
+ }
+ }
+ }
+
+ prevSearchInToken = searchInToken;
+ }
+
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return this._toFoundBracket(modeBrackets, r);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public findNextBracket(_position: IPosition): IFoundBracket | null {
+ const position = this.textModel.validatePosition(_position);
+ const lineCount = this.textModel.getLineCount();
+
+ let languageId: string | null = null;
+ let modeBrackets: RichEditBrackets | null = null;
+ for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
+ const lineTokens = this.textModel.getLineTokens(lineNumber);
+ const tokenCount = lineTokens.getCount();
+ const lineText = this.textModel.getLineContent(lineNumber);
+
+ let tokenIndex = 0;
+ let searchStartOffset = 0;
+ let searchEndOffset = 0;
+ if (lineNumber === position.lineNumber) {
+ tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
+ searchStartOffset = position.column - 1;
+ searchEndOffset = position.column - 1;
+ const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
+ if (languageId !== tokenLanguageId) {
+ languageId = tokenLanguageId;
+ modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
+ }
+ }
+
+ let prevSearchInToken = true;
+ for (; tokenIndex < tokenCount; tokenIndex++) {
+ const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
+
+ if (languageId !== tokenLanguageId) {
+ // language id change!
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return this._toFoundBracket(modeBrackets, r);
+ }
+ prevSearchInToken = false;
+ }
+ languageId = tokenLanguageId;
+ modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
+ }
+
+ const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
+ if (searchInToken) {
+ // this token should be searched
+ if (prevSearchInToken) {
+ // the previous token should be searched, simply extend searchEndOffset
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ } else {
+ // the previous token should not be searched
+ searchStartOffset = lineTokens.getStartOffset(tokenIndex);
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ }
+ } else {
+ // this token should not be searched
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return this._toFoundBracket(modeBrackets, r);
+ }
+ }
+ }
+
+ prevSearchInToken = searchInToken;
+ }
+
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return this._toFoundBracket(modeBrackets, r);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public findEnclosingBrackets(_position: IPosition, maxDuration?: number): [Range, Range] | null {
+ let continueSearchPredicate: ContinueBracketSearchPredicate;
+ if (typeof maxDuration === 'undefined') {
+ continueSearchPredicate = null;
+ } else {
+ const startTime = Date.now();
+ continueSearchPredicate = () => {
+ return (Date.now() - startTime <= maxDuration);
+ };
+ }
+ const position = this.textModel.validatePosition(_position);
+ const lineCount = this.textModel.getLineCount();
+ const savedCounts = new Map();
+
+ let counts: number[] = [];
+ const resetCounts = (languageId: string, modeBrackets: RichEditBrackets | null) => {
+ if (!savedCounts.has(languageId)) {
+ let tmp = [];
+ for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) {
+ tmp[i] = 0;
+ }
+ savedCounts.set(languageId, tmp);
+ }
+ counts = savedCounts.get(languageId)!;
+ };
+
+ let totalCallCount = 0;
+ const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null | BracketSearchCanceled => {
+ while (true) {
+ if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
+ return BracketSearchCanceled.INSTANCE;
+ }
+ const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (!r) {
+ break;
+ }
+
+ const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
+ const bracket = modeBrackets.textIsBracket[hitText];
+ if (bracket) {
+ if (bracket.isOpen(hitText)) {
+ counts[bracket.index]++;
+ } else if (bracket.isClose(hitText)) {
+ counts[bracket.index]--;
+ }
+
+ if (counts[bracket.index] === -1) {
+ return this._matchFoundBracket(r, bracket, false, continueSearchPredicate);
+ }
+ }
+
+ searchStartOffset = r.endColumn - 1;
+ }
+ return null;
+ };
+
+ let languageId: string | null = null;
+ let modeBrackets: RichEditBrackets | null = null;
+ for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
+ const lineTokens = this.textModel.getLineTokens(lineNumber);
+ const tokenCount = lineTokens.getCount();
+ const lineText = this.textModel.getLineContent(lineNumber);
+
+ let tokenIndex = 0;
+ let searchStartOffset = 0;
+ let searchEndOffset = 0;
+ if (lineNumber === position.lineNumber) {
+ tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
+ searchStartOffset = position.column - 1;
+ searchEndOffset = position.column - 1;
+ const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
+ if (languageId !== tokenLanguageId) {
+ languageId = tokenLanguageId;
+ modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
+ resetCounts(languageId, modeBrackets);
+ }
+ }
+
+ let prevSearchInToken = true;
+ for (; tokenIndex < tokenCount; tokenIndex++) {
+ const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
+
+ if (languageId !== tokenLanguageId) {
+ // language id change!
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return stripBracketSearchCanceled(r);
+ }
+ prevSearchInToken = false;
+ }
+ languageId = tokenLanguageId;
+ modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
+ resetCounts(languageId, modeBrackets);
+ }
+
+ const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
+ if (searchInToken) {
+ // this token should be searched
+ if (prevSearchInToken) {
+ // the previous token should be searched, simply extend searchEndOffset
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ } else {
+ // the previous token should not be searched
+ searchStartOffset = lineTokens.getStartOffset(tokenIndex);
+ searchEndOffset = lineTokens.getEndOffset(tokenIndex);
+ }
+ } else {
+ // this token should not be searched
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return stripBracketSearchCanceled(r);
+ }
+ }
+ }
+
+ prevSearchInToken = searchInToken;
+ }
+
+ if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
+ const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
+ if (r) {
+ return stripBracketSearchCanceled(r);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private _toFoundBracket(modeBrackets: RichEditBrackets, r: Range): IFoundBracket | null {
+ if (!r) {
+ return null;
+ }
+
+ let text = this.textModel.getValueInRange(r);
+ text = text.toLowerCase();
+
+ let data = modeBrackets.textIsBracket[text];
+ if (!data) {
+ return null;
+ }
+
+ return {
+ range: r,
+ open: data.open,
+ close: data.close,
+ isOpen: modeBrackets.textIsOpenBracket[text]
+ };
}
}
@@ -114,213 +746,17 @@ function createDisposableRef(object: T, disposable?: IDisposable): IReference
};
}
-class ActiveBracketPairsImpl extends Disposable {
- private readonly didChangeEmitter = new Emitter();
+type ContinueBracketSearchPredicate = null | (() => boolean);
- /*
- There are two trees:
- * The initial tree that has no token information and is used for performant initial bracket colorization.
- * The tree that used token information to detect bracket pairs.
-
- To prevent flickering, we only switch from the initial tree to tree with token information
- when tokenization completes.
- Since the text can be edited while background tokenization is in progress, we need to update both trees.
- */
- private initialAstWithoutTokens: AstNode | undefined;
- private astWithTokens: AstNode | undefined;
-
- private readonly denseKeyProvider = new DenseKeyProvider();
- private readonly brackets = new LanguageAgnosticBracketTokens(this.denseKeyProvider, this.getLanguageConfiguration);
-
- public didLanguageChange(languageId: string): boolean {
- return this.brackets.didLanguageChange(languageId);
- }
-
- public readonly onDidChange = this.didChangeEmitter.event;
-
- public constructor(
- private readonly textModel: TextModel,
- private readonly getLanguageConfiguration: (languageId: string) => ResolvedLanguageConfiguration
- ) {
- super();
-
- this._register(textModel.onBackgroundTokenizationStateChanged(() => {
- if (textModel.backgroundTokenizationState === BackgroundTokenizationState.Completed) {
- const wasUndefined = this.initialAstWithoutTokens === undefined;
- // Clear the initial tree as we can use the tree with token information now.
- this.initialAstWithoutTokens = undefined;
- if (!wasUndefined) {
- this.didChangeEmitter.fire();
- }
- }
- }));
-
- this._register(textModel.onDidChangeTokens(({ ranges }) => {
- const edits = ranges.map(r =>
- new TextEditInfo(
- toLength(r.fromLineNumber - 1, 0),
- toLength(r.toLineNumber, 0),
- toLength(r.toLineNumber - r.fromLineNumber + 1, 0)
- )
- );
- this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false);
- if (!this.initialAstWithoutTokens) {
- this.didChangeEmitter.fire();
- }
- }));
-
- if (textModel.backgroundTokenizationState === BackgroundTokenizationState.Uninitialized) {
- // There are no token information yet
- const brackets = this.brackets.getSingleLanguageBracketTokens(this.textModel.getLanguageId());
- const tokenizer = new FastTokenizer(this.textModel.getValue(), brackets);
- this.initialAstWithoutTokens = parseDocument(tokenizer, [], undefined, true);
- this.astWithTokens = this.initialAstWithoutTokens;
- } else if (textModel.backgroundTokenizationState === BackgroundTokenizationState.Completed) {
- // Skip the initial ast, as there is no flickering.
- // Directly create the tree with token information.
- this.initialAstWithoutTokens = undefined;
- this.astWithTokens = this.parseDocumentFromTextBuffer([], undefined, false);
- } else if (textModel.backgroundTokenizationState === BackgroundTokenizationState.InProgress) {
- this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer([], undefined, true);
- this.astWithTokens = this.initialAstWithoutTokens;
- }
- }
-
- public handleContentChanged(change: IModelContentChangedEvent) {
- const edits = change.changes.map(c => {
- const range = Range.lift(c.range);
- return new TextEditInfo(
- positionToLength(range.getStartPosition()),
- positionToLength(range.getEndPosition()),
- lengthOfString(c.text)
- );
- }).reverse();
-
- this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false);
- if (this.initialAstWithoutTokens) {
- this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(edits, this.initialAstWithoutTokens, false);
- }
- }
-
- /**
- * @pure (only if isPure = true)
- */
- private parseDocumentFromTextBuffer(edits: TextEditInfo[], previousAst: AstNode | undefined, immutable: boolean): AstNode {
- // Is much faster if `isPure = false`.
- const isPure = false;
- const previousAstClone = isPure ? previousAst?.deepClone() : previousAst;
- const tokenizer = new TextBufferTokenizer(this.textModel, this.brackets);
- const result = parseDocument(tokenizer, edits, previousAstClone, immutable);
- return result;
- }
-
- public getBracketsInRange(range: Range): BracketInfo[] {
- const startOffset = toLength(range.startLineNumber - 1, range.startColumn - 1);
- const endOffset = toLength(range.endLineNumber - 1, range.endColumn - 1);
- const result = new Array();
- const node = this.initialAstWithoutTokens || this.astWithTokens!;
- collectBrackets(node, lengthZero, node.length, startOffset, endOffset, result);
- return result;
- }
-
- public getBracketPairsInRange(range: Range, includeMinIndentation: boolean): BracketPairWithMinIndentationInfo[] {
- const result = new Array();
-
- const startLength = positionToLength(range.getStartPosition());
- const endLength = positionToLength(range.getEndPosition());
-
- const node = this.initialAstWithoutTokens || this.astWithTokens!;
- const context = new CollectBracketPairsContext(result, includeMinIndentation, this.textModel);
- collectBracketPairs(node, lengthZero, node.length, startLength, endLength, context);
-
- return result;
- }
+class BracketSearchCanceled {
+ public static INSTANCE = new BracketSearchCanceled();
+ _searchCanceledBrand = undefined;
+ private constructor() { }
}
-function collectBrackets(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, startOffset: Length, endOffset: Length, result: BracketInfo[], level: number = 0): void {
- if (node.kind === AstNodeKind.Bracket) {
- const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
- result.push(new BracketInfo(range, level - 1, false));
- } else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {
- const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
- result.push(new BracketInfo(range, level - 1, true));
- } else if (node.kind === AstNodeKind.List) {
- for (const child of node.children) {
- nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
- if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
- collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
- }
- nodeOffsetStart = nodeOffsetEnd;
- }
- } else if (node.kind === AstNodeKind.Pair) {
- // Don't use node.children here to improve performance
- level++;
-
- {
- const child = node.openingBracket;
- nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
- if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
- collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
- }
- nodeOffsetStart = nodeOffsetEnd;
- }
-
- if (node.child) {
- const child = node.child;
- nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
- if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
- collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
- }
- nodeOffsetStart = nodeOffsetEnd;
- }
- if (node.closingBracket) {
- const child = node.closingBracket;
- nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
- if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
- collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
- }
- nodeOffsetStart = nodeOffsetEnd;
- }
+function stripBracketSearchCanceled(result: T | null | BracketSearchCanceled): T | null {
+ if (result instanceof BracketSearchCanceled) {
+ return null;
}
+ return result;
}
-
-class CollectBracketPairsContext {
- constructor(
- public readonly result: BracketPairWithMinIndentationInfo[],
- public readonly includeMinIndentation: boolean,
- public readonly textModel: ITextModel,
- ) {
- }
-}
-
-function collectBracketPairs(node: AstNode, nodeOffset: Length, nodeOffsetEnd: Length, startOffset: Length, endOffset: Length, context: CollectBracketPairsContext, level: number = 0) {
- if (node.kind === AstNodeKind.Pair) {
- const openingBracketEnd = lengthAdd(nodeOffset, node.openingBracket.length);
- let minIndentation = -1;
- if (context.includeMinIndentation) {
- minIndentation = node.computeMinIndentation(nodeOffset, context.textModel);
- }
-
- context.result.push(new BracketPairWithMinIndentationInfo(
- lengthsToRange(nodeOffset, nodeOffsetEnd),
- lengthsToRange(nodeOffset, openingBracketEnd),
- node.closingBracket
- ? lengthsToRange(lengthAdd(openingBracketEnd, node.child?.length || lengthZero), nodeOffsetEnd)
- : undefined,
- level,
- minIndentation
- ));
- level++;
- }
-
- let curOffset = nodeOffset;
- for (const child of node.children) {
- const childOffset = curOffset;
- curOffset = lengthAdd(curOffset, child.length);
-
- if (lengthLessThanEqual(childOffset, endOffset) && lengthLessThanEqual(startOffset, curOffset)) {
- collectBracketPairs(child, childOffset, curOffset, startOffset, endOffset, context, level);
- }
- }
-}
-
diff --git a/src/vs/editor/common/model/bracketPairs/impl/ast.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/ast.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/ast.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/ast.ts
diff --git a/src/vs/editor/common/model/bracketPairs/impl/beforeEditPositionMapper.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/beforeEditPositionMapper.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/beforeEditPositionMapper.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/beforeEditPositionMapper.ts
diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree.ts
new file mode 100644
index 00000000000..450d570e6fe
--- /dev/null
+++ b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree.ts
@@ -0,0 +1,231 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Emitter } from 'vs/base/common/event';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { Range } from 'vs/editor/common/core/range';
+import { ITextModel } from 'vs/editor/common/model';
+import { BracketInfo, BracketPairWithMinIndentationInfo } from 'vs/editor/common/model/bracketPairs/bracketPairs';
+import { BackgroundTokenizationState, TextModel } from 'vs/editor/common/model/textModel';
+import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
+import { ResolvedLanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry';
+import { AstNode, AstNodeKind } from './ast';
+import { TextEditInfo } from './beforeEditPositionMapper';
+import { LanguageAgnosticBracketTokens } from './brackets';
+import { Length, lengthAdd, lengthGreaterThanEqual, lengthLessThanEqual, lengthOfString, lengthsToRange, lengthZero, positionToLength, toLength } from './length';
+import { parseDocument } from './parser';
+import { DenseKeyProvider } from './smallImmutableSet';
+import { FastTokenizer, TextBufferTokenizer } from './tokenizer';
+
+export class BracketPairsTree extends Disposable {
+ private readonly didChangeEmitter = new Emitter();
+
+ /*
+ There are two trees:
+ * The initial tree that has no token information and is used for performant initial bracket colorization.
+ * The tree that used token information to detect bracket pairs.
+
+ To prevent flickering, we only switch from the initial tree to tree with token information
+ when tokenization completes.
+ Since the text can be edited while background tokenization is in progress, we need to update both trees.
+ */
+ private initialAstWithoutTokens: AstNode | undefined;
+ private astWithTokens: AstNode | undefined;
+
+ private readonly denseKeyProvider = new DenseKeyProvider();
+ private readonly brackets = new LanguageAgnosticBracketTokens(this.denseKeyProvider, this.getLanguageConfiguration);
+
+ public didLanguageChange(languageId: string): boolean {
+ return this.brackets.didLanguageChange(languageId);
+ }
+
+ public readonly onDidChange = this.didChangeEmitter.event;
+
+ public constructor(
+ private readonly textModel: TextModel,
+ private readonly getLanguageConfiguration: (languageId: string) => ResolvedLanguageConfiguration
+ ) {
+ super();
+
+ this._register(textModel.onBackgroundTokenizationStateChanged(() => {
+ if (textModel.backgroundTokenizationState === BackgroundTokenizationState.Completed) {
+ const wasUndefined = this.initialAstWithoutTokens === undefined;
+ // Clear the initial tree as we can use the tree with token information now.
+ this.initialAstWithoutTokens = undefined;
+ if (!wasUndefined) {
+ this.didChangeEmitter.fire();
+ }
+ }
+ }));
+
+ this._register(textModel.onDidChangeTokens(({ ranges }) => {
+ const edits = ranges.map(r =>
+ new TextEditInfo(
+ toLength(r.fromLineNumber - 1, 0),
+ toLength(r.toLineNumber, 0),
+ toLength(r.toLineNumber - r.fromLineNumber + 1, 0)
+ )
+ );
+ this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false);
+ if (!this.initialAstWithoutTokens) {
+ this.didChangeEmitter.fire();
+ }
+ }));
+
+ if (textModel.backgroundTokenizationState === BackgroundTokenizationState.Uninitialized) {
+ // There are no token information yet
+ const brackets = this.brackets.getSingleLanguageBracketTokens(this.textModel.getLanguageId());
+ const tokenizer = new FastTokenizer(this.textModel.getValue(), brackets);
+ this.initialAstWithoutTokens = parseDocument(tokenizer, [], undefined, true);
+ this.astWithTokens = this.initialAstWithoutTokens;
+ } else if (textModel.backgroundTokenizationState === BackgroundTokenizationState.Completed) {
+ // Skip the initial ast, as there is no flickering.
+ // Directly create the tree with token information.
+ this.initialAstWithoutTokens = undefined;
+ this.astWithTokens = this.parseDocumentFromTextBuffer([], undefined, false);
+ } else if (textModel.backgroundTokenizationState === BackgroundTokenizationState.InProgress) {
+ this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer([], undefined, true);
+ this.astWithTokens = this.initialAstWithoutTokens;
+ }
+ }
+
+ public handleContentChanged(change: IModelContentChangedEvent) {
+ const edits = change.changes.map(c => {
+ const range = Range.lift(c.range);
+ return new TextEditInfo(
+ positionToLength(range.getStartPosition()),
+ positionToLength(range.getEndPosition()),
+ lengthOfString(c.text)
+ );
+ }).reverse();
+
+ this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false);
+ if (this.initialAstWithoutTokens) {
+ this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(edits, this.initialAstWithoutTokens, false);
+ }
+ }
+
+ /**
+ * @pure (only if isPure = true)
+ */
+ private parseDocumentFromTextBuffer(edits: TextEditInfo[], previousAst: AstNode | undefined, immutable: boolean): AstNode {
+ // Is much faster if `isPure = false`.
+ const isPure = false;
+ const previousAstClone = isPure ? previousAst?.deepClone() : previousAst;
+ const tokenizer = new TextBufferTokenizer(this.textModel, this.brackets);
+ const result = parseDocument(tokenizer, edits, previousAstClone, immutable);
+ return result;
+ }
+
+ public getBracketsInRange(range: Range): BracketInfo[] {
+ const startOffset = toLength(range.startLineNumber - 1, range.startColumn - 1);
+ const endOffset = toLength(range.endLineNumber - 1, range.endColumn - 1);
+ const result = new Array();
+ const node = this.initialAstWithoutTokens || this.astWithTokens!;
+ collectBrackets(node, lengthZero, node.length, startOffset, endOffset, result);
+ return result;
+ }
+
+ public getBracketPairsInRange(range: Range, includeMinIndentation: boolean): BracketPairWithMinIndentationInfo[] {
+ const result = new Array();
+
+ const startLength = positionToLength(range.getStartPosition());
+ const endLength = positionToLength(range.getEndPosition());
+
+ const node = this.initialAstWithoutTokens || this.astWithTokens!;
+ const context = new CollectBracketPairsContext(result, includeMinIndentation, this.textModel);
+ collectBracketPairs(node, lengthZero, node.length, startLength, endLength, context);
+
+ return result;
+ }
+}
+
+function collectBrackets(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, startOffset: Length, endOffset: Length, result: BracketInfo[], level: number = 0): void {
+ if (node.kind === AstNodeKind.Bracket) {
+ const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
+ result.push(new BracketInfo(range, level - 1, false));
+ } else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {
+ const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
+ result.push(new BracketInfo(range, level - 1, true));
+ } else if (node.kind === AstNodeKind.List) {
+ for (const child of node.children) {
+ nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
+ if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
+ collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
+ }
+ nodeOffsetStart = nodeOffsetEnd;
+ }
+ } else if (node.kind === AstNodeKind.Pair) {
+ // Don't use node.children here to improve performance
+ level++;
+
+ {
+ const child = node.openingBracket;
+ nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
+ if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
+ collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
+ }
+ nodeOffsetStart = nodeOffsetEnd;
+ }
+
+ if (node.child) {
+ const child = node.child;
+ nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
+ if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
+ collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
+ }
+ nodeOffsetStart = nodeOffsetEnd;
+ }
+ if (node.closingBracket) {
+ const child = node.closingBracket;
+ nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
+ if (lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset)) {
+ collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, result, level);
+ }
+ nodeOffsetStart = nodeOffsetEnd;
+ }
+ }
+}
+
+class CollectBracketPairsContext {
+ constructor(
+ public readonly result: BracketPairWithMinIndentationInfo[],
+ public readonly includeMinIndentation: boolean,
+ public readonly textModel: ITextModel,
+ ) {
+ }
+}
+
+function collectBracketPairs(node: AstNode, nodeOffset: Length, nodeOffsetEnd: Length, startOffset: Length, endOffset: Length, context: CollectBracketPairsContext, level: number = 0) {
+ if (node.kind === AstNodeKind.Pair) {
+ const openingBracketEnd = lengthAdd(nodeOffset, node.openingBracket.length);
+ let minIndentation = -1;
+ if (context.includeMinIndentation) {
+ minIndentation = node.computeMinIndentation(nodeOffset, context.textModel);
+ }
+
+ context.result.push(new BracketPairWithMinIndentationInfo(
+ lengthsToRange(nodeOffset, nodeOffsetEnd),
+ lengthsToRange(nodeOffset, openingBracketEnd),
+ node.closingBracket
+ ? lengthsToRange(lengthAdd(openingBracketEnd, node.child?.length || lengthZero), nodeOffsetEnd)
+ : undefined,
+ level,
+ minIndentation
+ ));
+ level++;
+ }
+
+ let curOffset = nodeOffset;
+ for (const child of node.children) {
+ const childOffset = curOffset;
+ curOffset = lengthAdd(curOffset, child.length);
+
+ if (lengthLessThanEqual(childOffset, endOffset) && lengthLessThanEqual(startOffset, curOffset)) {
+ collectBracketPairs(child, childOffset, curOffset, startOffset, endOffset, context, level);
+ }
+ }
+}
+
diff --git a/src/vs/editor/common/model/bracketPairs/impl/brackets.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/brackets.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/brackets.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/brackets.ts
diff --git a/src/vs/editor/common/model/bracketPairs/impl/concat23Trees.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/concat23Trees.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/concat23Trees.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/concat23Trees.ts
diff --git a/src/vs/editor/common/model/bracketPairs/impl/length.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/length.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/length.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/length.ts
diff --git a/src/vs/editor/common/model/bracketPairs/impl/nodeReader.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/nodeReader.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/nodeReader.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/nodeReader.ts
diff --git a/src/vs/editor/common/model/bracketPairs/impl/parser.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/parser.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/parser.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/parser.ts
diff --git a/src/vs/editor/common/model/bracketPairs/impl/smallImmutableSet.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/smallImmutableSet.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet.ts
diff --git a/src/vs/editor/common/model/bracketPairs/impl/tokenizer.ts b/src/vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer.ts
similarity index 100%
rename from src/vs/editor/common/model/bracketPairs/impl/tokenizer.ts
rename to src/vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer.ts
diff --git a/src/vs/editor/common/model/mirrorTextModel.ts b/src/vs/editor/common/model/mirrorTextModel.ts
index 4fefa551a1a..9ff680f395f 100644
--- a/src/vs/editor/common/model/mirrorTextModel.ts
+++ b/src/vs/editor/common/model/mirrorTextModel.ts
@@ -106,7 +106,7 @@ export class MirrorTextModel implements IMirrorTextModel {
this._lines[lineIndex] = newValue;
if (this._lineStarts) {
// update prefix sum
- this._lineStarts.changeValue(lineIndex, this._lines[lineIndex].length + this._eol.length);
+ this._lineStarts.setValue(lineIndex, this._lines[lineIndex].length + this._eol.length);
}
}
diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts
index 51b7208cdb8..828352da600 100644
--- a/src/vs/editor/common/model/textModel.ts
+++ b/src/vs/editor/common/model/textModel.ts
@@ -27,8 +27,6 @@ import { getWordAtText } from 'vs/editor/common/model/wordHelper';
import { FormattingOptions } from 'vs/editor/common/modes';
import { ILanguageConfigurationService, ResolvedLanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { NULL_MODE_ID } from 'vs/editor/common/modes/nullMode';
-import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports';
-import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore';
@@ -174,21 +172,6 @@ export const enum BackgroundTokenizationState {
Completed = 2,
}
-type ContinueBracketSearchPredicate = null | (() => boolean);
-
-class BracketSearchCanceled {
- public static INSTANCE = new BracketSearchCanceled();
- _searchCanceledBrand = undefined;
- private constructor() { }
-}
-
-function stripBracketSearchCanceled(result: T | null | BracketSearchCanceled): T | null {
- if (result instanceof BracketSearchCanceled) {
- return null;
- }
- return result;
-}
-
export class TextModel extends Disposable implements model.ITextModel, IDecorationsTreesHost {
private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
@@ -2222,642 +2205,6 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
};
}
- public findMatchingBracketUp(_bracket: string, _position: IPosition): Range | null {
- let bracket = _bracket.toLowerCase();
- let position = this.validatePosition(_position);
-
- let lineTokens = this._getLineTokens(position.lineNumber);
- let languageId = lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1));
- let bracketsSupport = this.getLanguageConfiguration(languageId).brackets;
-
- if (!bracketsSupport) {
- return null;
- }
-
- let data = bracketsSupport.textIsBracket[bracket];
-
- if (!data) {
- return null;
- }
-
- return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, null));
- }
-
- public matchBracket(position: IPosition): [Range, Range] | null {
- return this._matchBracket(this.validatePosition(position));
- }
-
- private _establishBracketSearchOffsets(position: Position, lineTokens: LineTokens, modeBrackets: RichEditBrackets, tokenIndex: number) {
- const tokenCount = lineTokens.getCount();
- const currentLanguageId = lineTokens.getLanguageId(tokenIndex);
-
- // limit search to not go before `maxBracketLength`
- let searchStartOffset = Math.max(0, position.column - 1 - modeBrackets.maxBracketLength);
- for (let i = tokenIndex - 1; i >= 0; i--) {
- const tokenEndOffset = lineTokens.getEndOffset(i);
- if (tokenEndOffset <= searchStartOffset) {
- break;
- }
- if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
- searchStartOffset = tokenEndOffset;
- break;
- }
- }
-
- // limit search to not go after `maxBracketLength`
- let searchEndOffset = Math.min(lineTokens.getLineContent().length, position.column - 1 + modeBrackets.maxBracketLength);
- for (let i = tokenIndex + 1; i < tokenCount; i++) {
- const tokenStartOffset = lineTokens.getStartOffset(i);
- if (tokenStartOffset >= searchEndOffset) {
- break;
- }
- if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
- searchEndOffset = tokenStartOffset;
- break;
- }
- }
-
- return { searchStartOffset, searchEndOffset };
- }
-
- private _matchBracket(position: Position): [Range, Range] | null {
- const lineNumber = position.lineNumber;
- const lineTokens = this._getLineTokens(lineNumber);
- const lineText = this._buffer.getLineContent(lineNumber);
-
- const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
- if (tokenIndex < 0) {
- return null;
- }
- const currentModeBrackets = this.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex)).brackets;
-
- // check that the token is not to be ignored
- if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) {
-
- let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, currentModeBrackets, tokenIndex);
-
- // it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
- // `bestResult` will contain the most right-side result
- let bestResult: [Range, Range] | null = null;
- while (true) {
- const foundBracket = BracketsUtils.findNextBracketInRange(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (!foundBracket) {
- // there are no more brackets in this text
- break;
- }
-
- // check that we didn't hit a bracket too far away from position
- if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
- const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
- const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], null);
- if (r) {
- if (r instanceof BracketSearchCanceled) {
- return null;
- }
- bestResult = r;
- }
- }
-
- searchStartOffset = foundBracket.endColumn - 1;
- }
-
- if (bestResult) {
- return bestResult;
- }
- }
-
- // If position is in between two tokens, try also looking in the previous token
- if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) {
- const prevTokenIndex = tokenIndex - 1;
- const prevModeBrackets = this.getLanguageConfiguration(lineTokens.getLanguageId(prevTokenIndex)).brackets;
-
- // check that previous token is not to be ignored
- if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) {
-
- let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, prevModeBrackets, prevTokenIndex);
-
- const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
-
- // check that we didn't hit a bracket too far away from position
- if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
- const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
- const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], null);
- if (r) {
- if (r instanceof BracketSearchCanceled) {
- return null;
- }
- return r;
- }
- }
- }
- }
-
- return null;
- }
-
- private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null | BracketSearchCanceled {
- if (!data) {
- return null;
- }
-
- const matched = (
- isOpen
- ? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate)
- : this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate)
- );
-
- if (!matched) {
- return null;
- }
-
- if (matched instanceof BracketSearchCanceled) {
- return matched;
- }
-
- return [foundBracket, matched];
- }
-
- private _findMatchingBracketUp(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
- // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
-
- const languageId = bracket.languageId;
- const reversedBracketRegex = bracket.reversedRegex;
- let count = -1;
-
- let totalCallCount = 0;
- const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
- while (true) {
- if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
- return BracketSearchCanceled.INSTANCE;
- }
- const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (!r) {
- break;
- }
-
- const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
- if (bracket.isOpen(hitText)) {
- count++;
- } else if (bracket.isClose(hitText)) {
- count--;
- }
-
- if (count === 0) {
- return r;
- }
-
- searchEndOffset = r.startColumn - 1;
- }
-
- return null;
- };
-
- for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {
- const lineTokens = this._getLineTokens(lineNumber);
- const tokenCount = lineTokens.getCount();
- const lineText = this._buffer.getLineContent(lineNumber);
-
- let tokenIndex = tokenCount - 1;
- let searchStartOffset = lineText.length;
- let searchEndOffset = lineText.length;
- if (lineNumber === position.lineNumber) {
- tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
- searchStartOffset = position.column - 1;
- searchEndOffset = position.column - 1;
- }
-
- let prevSearchInToken = true;
- for (; tokenIndex >= 0; tokenIndex--) {
- const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
-
- if (searchInToken) {
- // this token should be searched
- if (prevSearchInToken) {
- // the previous token should be searched, simply extend searchStartOffset
- searchStartOffset = lineTokens.getStartOffset(tokenIndex);
- } else {
- // the previous token should not be searched
- searchStartOffset = lineTokens.getStartOffset(tokenIndex);
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- }
- } else {
- // this token should not be searched
- if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return r;
- }
- }
- }
-
- prevSearchInToken = searchInToken;
- }
-
- if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return r;
- }
- }
- }
-
- return null;
- }
-
- private _findMatchingBracketDown(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
- // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
-
- const languageId = bracket.languageId;
- const bracketRegex = bracket.forwardRegex;
- let count = 1;
-
- let totalCallCount = 0;
- const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
- while (true) {
- if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
- return BracketSearchCanceled.INSTANCE;
- }
- const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (!r) {
- break;
- }
-
- const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
- if (bracket.isOpen(hitText)) {
- count++;
- } else if (bracket.isClose(hitText)) {
- count--;
- }
-
- if (count === 0) {
- return r;
- }
-
- searchStartOffset = r.endColumn - 1;
- }
-
- return null;
- };
-
- const lineCount = this.getLineCount();
- for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
- const lineTokens = this._getLineTokens(lineNumber);
- const tokenCount = lineTokens.getCount();
- const lineText = this._buffer.getLineContent(lineNumber);
-
- let tokenIndex = 0;
- let searchStartOffset = 0;
- let searchEndOffset = 0;
- if (lineNumber === position.lineNumber) {
- tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
- searchStartOffset = position.column - 1;
- searchEndOffset = position.column - 1;
- }
-
- let prevSearchInToken = true;
- for (; tokenIndex < tokenCount; tokenIndex++) {
- const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
-
- if (searchInToken) {
- // this token should be searched
- if (prevSearchInToken) {
- // the previous token should be searched, simply extend searchEndOffset
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- } else {
- // the previous token should not be searched
- searchStartOffset = lineTokens.getStartOffset(tokenIndex);
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- }
- } else {
- // this token should not be searched
- if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return r;
- }
- }
- }
-
- prevSearchInToken = searchInToken;
- }
-
- if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return r;
- }
- }
- }
-
- return null;
- }
-
- public findPrevBracket(_position: IPosition): model.IFoundBracket | null {
- const position = this.validatePosition(_position);
-
- let languageId: string | null = null;
- let modeBrackets: RichEditBrackets | null = null;
- for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {
- const lineTokens = this._getLineTokens(lineNumber);
- const tokenCount = lineTokens.getCount();
- const lineText = this._buffer.getLineContent(lineNumber);
-
- let tokenIndex = tokenCount - 1;
- let searchStartOffset = lineText.length;
- let searchEndOffset = lineText.length;
- if (lineNumber === position.lineNumber) {
- tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
- searchStartOffset = position.column - 1;
- searchEndOffset = position.column - 1;
- const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
- if (languageId !== tokenLanguageId) {
- languageId = tokenLanguageId;
- modeBrackets = this.getLanguageConfiguration(languageId).brackets;
- }
- }
-
- let prevSearchInToken = true;
- for (; tokenIndex >= 0; tokenIndex--) {
- const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
-
- if (languageId !== tokenLanguageId) {
- // language id change!
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return this._toFoundBracket(modeBrackets, r);
- }
- prevSearchInToken = false;
- }
- languageId = tokenLanguageId;
- modeBrackets = this.getLanguageConfiguration(languageId).brackets;
- }
-
- const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
-
- if (searchInToken) {
- // this token should be searched
- if (prevSearchInToken) {
- // the previous token should be searched, simply extend searchStartOffset
- searchStartOffset = lineTokens.getStartOffset(tokenIndex);
- } else {
- // the previous token should not be searched
- searchStartOffset = lineTokens.getStartOffset(tokenIndex);
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- }
- } else {
- // this token should not be searched
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return this._toFoundBracket(modeBrackets, r);
- }
- }
- }
-
- prevSearchInToken = searchInToken;
- }
-
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return this._toFoundBracket(modeBrackets, r);
- }
- }
- }
-
- return null;
- }
-
- public findNextBracket(_position: IPosition): model.IFoundBracket | null {
- const position = this.validatePosition(_position);
- const lineCount = this.getLineCount();
-
- let languageId: string | null = null;
- let modeBrackets: RichEditBrackets | null = null;
- for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
- const lineTokens = this._getLineTokens(lineNumber);
- const tokenCount = lineTokens.getCount();
- const lineText = this._buffer.getLineContent(lineNumber);
-
- let tokenIndex = 0;
- let searchStartOffset = 0;
- let searchEndOffset = 0;
- if (lineNumber === position.lineNumber) {
- tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
- searchStartOffset = position.column - 1;
- searchEndOffset = position.column - 1;
- const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
- if (languageId !== tokenLanguageId) {
- languageId = tokenLanguageId;
- modeBrackets = this.getLanguageConfiguration(languageId).brackets;
- }
- }
-
- let prevSearchInToken = true;
- for (; tokenIndex < tokenCount; tokenIndex++) {
- const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
-
- if (languageId !== tokenLanguageId) {
- // language id change!
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return this._toFoundBracket(modeBrackets, r);
- }
- prevSearchInToken = false;
- }
- languageId = tokenLanguageId;
- modeBrackets = this.getLanguageConfiguration(languageId).brackets;
- }
-
- const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
- if (searchInToken) {
- // this token should be searched
- if (prevSearchInToken) {
- // the previous token should be searched, simply extend searchEndOffset
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- } else {
- // the previous token should not be searched
- searchStartOffset = lineTokens.getStartOffset(tokenIndex);
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- }
- } else {
- // this token should not be searched
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return this._toFoundBracket(modeBrackets, r);
- }
- }
- }
-
- prevSearchInToken = searchInToken;
- }
-
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return this._toFoundBracket(modeBrackets, r);
- }
- }
- }
-
- return null;
- }
-
- public findEnclosingBrackets(_position: IPosition, maxDuration?: number): [Range, Range] | null {
- let continueSearchPredicate: ContinueBracketSearchPredicate;
- if (typeof maxDuration === 'undefined') {
- continueSearchPredicate = null;
- } else {
- const startTime = Date.now();
- continueSearchPredicate = () => {
- return (Date.now() - startTime <= maxDuration);
- };
- }
- const position = this.validatePosition(_position);
- const lineCount = this.getLineCount();
- const savedCounts = new Map();
-
- let counts: number[] = [];
- const resetCounts = (languageId: string, modeBrackets: RichEditBrackets | null) => {
- if (!savedCounts.has(languageId)) {
- let tmp = [];
- for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) {
- tmp[i] = 0;
- }
- savedCounts.set(languageId, tmp);
- }
- counts = savedCounts.get(languageId)!;
- };
-
- let totalCallCount = 0;
- const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null | BracketSearchCanceled => {
- while (true) {
- if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
- return BracketSearchCanceled.INSTANCE;
- }
- const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (!r) {
- break;
- }
-
- const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
- const bracket = modeBrackets.textIsBracket[hitText];
- if (bracket) {
- if (bracket.isOpen(hitText)) {
- counts[bracket.index]++;
- } else if (bracket.isClose(hitText)) {
- counts[bracket.index]--;
- }
-
- if (counts[bracket.index] === -1) {
- return this._matchFoundBracket(r, bracket, false, continueSearchPredicate);
- }
- }
-
- searchStartOffset = r.endColumn - 1;
- }
- return null;
- };
-
- let languageId: string | null = null;
- let modeBrackets: RichEditBrackets | null = null;
- for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
- const lineTokens = this._getLineTokens(lineNumber);
- const tokenCount = lineTokens.getCount();
- const lineText = this._buffer.getLineContent(lineNumber);
-
- let tokenIndex = 0;
- let searchStartOffset = 0;
- let searchEndOffset = 0;
- if (lineNumber === position.lineNumber) {
- tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
- searchStartOffset = position.column - 1;
- searchEndOffset = position.column - 1;
- const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
- if (languageId !== tokenLanguageId) {
- languageId = tokenLanguageId;
- modeBrackets = this.getLanguageConfiguration(languageId).brackets;
- resetCounts(languageId, modeBrackets);
- }
- }
-
- let prevSearchInToken = true;
- for (; tokenIndex < tokenCount; tokenIndex++) {
- const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
-
- if (languageId !== tokenLanguageId) {
- // language id change!
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return stripBracketSearchCanceled(r);
- }
- prevSearchInToken = false;
- }
- languageId = tokenLanguageId;
- modeBrackets = this.getLanguageConfiguration(languageId).brackets;
- resetCounts(languageId, modeBrackets);
- }
-
- const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
- if (searchInToken) {
- // this token should be searched
- if (prevSearchInToken) {
- // the previous token should be searched, simply extend searchEndOffset
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- } else {
- // the previous token should not be searched
- searchStartOffset = lineTokens.getStartOffset(tokenIndex);
- searchEndOffset = lineTokens.getEndOffset(tokenIndex);
- }
- } else {
- // this token should not be searched
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return stripBracketSearchCanceled(r);
- }
- }
- }
-
- prevSearchInToken = searchInToken;
- }
-
- if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
- const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
- if (r) {
- return stripBracketSearchCanceled(r);
- }
- }
- }
-
- return null;
- }
-
- private _toFoundBracket(modeBrackets: RichEditBrackets, r: Range): model.IFoundBracket | null {
- if (!r) {
- return null;
- }
-
- let text = this.getValueInRange(r);
- text = text.toLowerCase();
-
- let data = modeBrackets.textIsBracket[text];
- if (!data) {
- return null;
- }
-
- return {
- range: r,
- open: data.open,
- close: data.close,
- isOpen: modeBrackets.textIsOpenBracket[text]
- };
- }
-
/**
* Returns:
* - -1 => the line consists of whitespace
diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts
index 6b6085c4927..f2f40d007d4 100644
--- a/src/vs/editor/common/modes.ts
+++ b/src/vs/editor/common/modes.ts
@@ -483,6 +483,11 @@ export const enum CompletionItemInsertTextRule {
InsertAsSnippet = 0b100,
}
+export interface CompletionItemRanges {
+ insert: IRange;
+ replace: IRange;
+}
+
/**
* A completion item represents a text snippet that is
* proposed to complete text that is being typed.
@@ -551,7 +556,7 @@ export interface CompletionItem {
* *Note:* The range must be a {@link Range.isSingleLine single line} and it must
* {@link Range.contains contain} the position at which completion has been {@link CompletionItemProvider.provideCompletionItems requested}.
*/
- range: IRange | { insert: IRange, replace: IRange };
+ range: IRange | CompletionItemRanges;
/**
* An optional set of characters that when pressed while this completion is active will accept it first and
* then type that character. *Note* that all commit characters should have `length=1` and that superfluous
diff --git a/src/vs/editor/common/modes/tokenization/typescript.ts b/src/vs/editor/common/modes/tokenization/typescript.ts
deleted file mode 100644
index 207e8f492ee..00000000000
--- a/src/vs/editor/common/modes/tokenization/typescript.ts
+++ /dev/null
@@ -1,304 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { StandardTokenType } from 'vs/editor/common/modes';
-import { CharCode } from 'vs/base/common/charCode';
-
-class ParserContext {
- public readonly text: string;
- public readonly len: number;
- public readonly tokens: number[];
- public pos: number;
-
- private currentTokenStartOffset: number;
- private currentTokenType: StandardTokenType;
-
- constructor(text: string) {
- this.text = text;
- this.len = this.text.length;
- this.tokens = [];
- this.pos = 0;
- this.currentTokenStartOffset = 0;
- this.currentTokenType = StandardTokenType.Other;
- }
-
- private _safeCharCodeAt(index: number): number {
- if (index >= this.len) {
- return CharCode.Null;
- }
- return this.text.charCodeAt(index);
- }
-
- peek(distance: number = 0): number {
- return this._safeCharCodeAt(this.pos + distance);
- }
-
- next(): number {
- const result = this._safeCharCodeAt(this.pos);
- this.pos++;
- return result;
- }
-
- advance(distance: number): void {
- this.pos += distance;
- }
-
- eof(): boolean {
- return this.pos >= this.len;
- }
-
- beginToken(tokenType: StandardTokenType, deltaPos: number = 0): void {
- this.currentTokenStartOffset = this.pos + deltaPos;
- this.currentTokenType = tokenType;
- }
-
- endToken(deltaPos: number = 0): void {
- const length = this.pos + deltaPos - this.currentTokenStartOffset;
- // check if it is touching previous token
- if (this.tokens.length > 0) {
- const previousStartOffset = this.tokens[this.tokens.length - 3];
- const previousLength = this.tokens[this.tokens.length - 2];
- const previousTokenType = this.tokens[this.tokens.length - 1];
- const previousEndOffset = previousStartOffset + previousLength;
- if (this.currentTokenStartOffset === previousEndOffset && previousTokenType === this.currentTokenType) {
- // extend previous token
- this.tokens[this.tokens.length - 2] += length;
- return;
- }
- }
- this.tokens.push(this.currentTokenStartOffset, length, this.currentTokenType);
- }
-}
-
-export function parse(text: string): number[] {
- const ctx = new ParserContext(text);
- while (!ctx.eof()) {
- parseRoot(ctx);
- }
- return ctx.tokens;
-}
-
-function parseRoot(ctx: ParserContext): void {
- let curlyCount = 0;
- while (!ctx.eof()) {
- const ch = ctx.peek();
-
- switch (ch) {
- case CharCode.SingleQuote:
- parseSimpleString(ctx, CharCode.SingleQuote);
- break;
- case CharCode.DoubleQuote:
- parseSimpleString(ctx, CharCode.DoubleQuote);
- break;
- case CharCode.BackTick:
- parseInterpolatedString(ctx);
- break;
- case CharCode.Slash:
- parseSlash(ctx);
- break;
- case CharCode.OpenCurlyBrace:
- ctx.advance(1);
- curlyCount++;
- break;
- case CharCode.CloseCurlyBrace:
- ctx.advance(1);
- curlyCount--;
- if (curlyCount < 0) {
- return;
- }
- break;
- default:
- ctx.advance(1);
- }
- }
-
-}
-
-function parseSimpleString(ctx: ParserContext, closingQuote: number): void {
- ctx.beginToken(StandardTokenType.String);
-
- // skip the opening quote
- ctx.advance(1);
-
- while (!ctx.eof()) {
- const ch = ctx.next();
- if (ch === CharCode.Backslash) {
- // skip \r\n or any other character following a backslash
- const advanceCount = (ctx.peek() === CharCode.CarriageReturn && ctx.peek(1) === CharCode.LineFeed ? 2 : 1);
- ctx.advance(advanceCount);
- } else if (ch === closingQuote) {
- // hit end quote, so stop
- break;
- }
- }
-
- ctx.endToken();
-}
-
-function parseInterpolatedString(ctx: ParserContext): void {
- ctx.beginToken(StandardTokenType.String);
-
- // skip the opening quote
- ctx.advance(1);
-
- while (!ctx.eof()) {
- const ch = ctx.next();
- if (ch === CharCode.Backslash) {
- // skip \r\n or any other character following a backslash
- const advanceCount = (ctx.peek() === CharCode.CarriageReturn && ctx.peek(1) === CharCode.LineFeed ? 2 : 1);
- ctx.advance(advanceCount);
- } else if (ch === CharCode.BackTick) {
- // hit end quote, so stop
- break;
- } else if (ch === CharCode.DollarSign) {
- if (ctx.peek() === CharCode.OpenCurlyBrace) {
- ctx.advance(1);
- ctx.endToken();
- parseRoot(ctx);
- ctx.beginToken(StandardTokenType.String, -1);
- }
- }
- }
-
- ctx.endToken();
-}
-
-function parseSlash(ctx: ParserContext): void {
-
- const nextCh = ctx.peek(1);
- if (nextCh === CharCode.Asterisk) {
- parseMultiLineComment(ctx);
- return;
- }
-
- if (nextCh === CharCode.Slash) {
- parseSingleLineComment(ctx);
- return;
- }
-
- if (tryParseRegex(ctx)) {
- return;
- }
-
- ctx.advance(1);
-}
-
-function tryParseRegex(ctx: ParserContext): boolean {
- // See https://www.ecma-international.org/ecma-262/10.0/index.html#prod-RegularExpressionLiteral
-
- // TODO: avoid regex...
- let contentBefore = ctx.text.substr(ctx.pos - 100, 100);
- if (/[a-zA-Z0-9](\s*)$/.test(contentBefore)) {
- // Cannot start after an identifier
- return false;
- }
-
- let pos = 0;
- let len = ctx.len - ctx.pos;
- let inClass = false;
-
- // skip /
- pos++;
-
- while (pos < len) {
- const ch = ctx.peek(pos++);
-
- if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) {
- return false;
- }
-
- if (ch === CharCode.Backslash) {
- const nextCh = ctx.peek();
- if (nextCh === CharCode.CarriageReturn || nextCh === CharCode.LineFeed) {
- return false;
- }
- // skip next character
- pos++;
- continue;
- }
-
- if (inClass) {
-
- if (ch === CharCode.CloseSquareBracket) {
- inClass = false;
- continue;
- }
-
- } else {
-
- if (ch === CharCode.Slash) {
- // cannot be directly followed by a /
- if (ctx.peek(pos) === CharCode.Slash) {
- return false;
- }
-
- // consume flags
- do {
- let nextCh = ctx.peek(pos);
- if (nextCh >= CharCode.a && nextCh <= CharCode.z) {
- pos++;
- continue;
- } else {
- break;
- }
- } while (true);
-
- // TODO: avoid regex...
- if (/^(\s*)(\.|;|\/|,|\)|\]|\}|$)/.test(ctx.text.substr(ctx.pos + pos))) {
- // Must be followed by an operator of kinds
- ctx.beginToken(StandardTokenType.RegEx);
- ctx.advance(pos);
- ctx.endToken();
- return true;
- }
-
- return false;
- }
-
- if (ch === CharCode.OpenSquareBracket) {
- inClass = true;
- continue;
- }
-
- }
- }
-
- return false;
-}
-
-function parseMultiLineComment(ctx: ParserContext): void {
- ctx.beginToken(StandardTokenType.Comment);
-
- // skip the /*
- ctx.advance(2);
-
- while (!ctx.eof()) {
- const ch = ctx.next();
- if (ch === CharCode.Asterisk) {
- if (ctx.peek() === CharCode.Slash) {
- ctx.advance(1);
- break;
- }
- }
- }
-
- ctx.endToken();
-}
-
-function parseSingleLineComment(ctx: ParserContext): void {
- ctx.beginToken(StandardTokenType.Comment);
-
- // skip the //
- ctx.advance(2);
-
- while (!ctx.eof()) {
- const ch = ctx.next();
- if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) {
- break;
- }
- }
-
- ctx.endToken();
-}
diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts
index d0950a54331..e1619ace439 100644
--- a/src/vs/editor/common/view/editorColorRegistry.ts
+++ b/src/vs/editor/common/view/editorColorRegistry.ts
@@ -45,6 +45,7 @@ export const editorUnnecessaryCodeOpacity = registerColor('editorUnnecessaryCode
export const ghostTextBorder = registerColor('editorGhostText.border', { dark: null, light: null, hc: Color.fromHex('#fff').transparent(0.8) }, nls.localize('editorGhostTextBorder', 'Border color of ghost text in the editor.'));
export const ghostTextForeground = registerColor('editorGhostText.foreground', { dark: Color.fromHex('#ffffff56'), light: Color.fromHex('#0007'), hc: null }, nls.localize('editorGhostTextForeground', 'Foreground color of the ghost text in the editor.'));
+export const ghostTextBackground = registerColor('editorGhostText.background', { dark: null, light: null, hc: null }, nls.localize('editorGhostTextBackground', 'Background color of the ghost text in the editor.'));
const rulerRangeDefault = new Color(new RGBA(0, 122, 204, 0.6));
export const overviewRulerRangeHighlight = registerColor('editorOverviewRuler.rangeHighlightForeground', { dark: rulerRangeDefault, light: rulerRangeDefault, hc: rulerRangeDefault }, nls.localize('overviewRulerRangeHighlight', 'Overview ruler marker color for range highlights. The color must not be opaque so as not to hide underlying decorations.'), true);
diff --git a/src/vs/editor/common/viewModel/prefixSumComputer.ts b/src/vs/editor/common/viewModel/prefixSumComputer.ts
index c64f57195b9..52d6f316dcf 100644
--- a/src/vs/editor/common/viewModel/prefixSumComputer.ts
+++ b/src/vs/editor/common/viewModel/prefixSumComputer.ts
@@ -3,20 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { arrayInsert } from 'vs/base/common/arrays';
import { toUint32 } from 'vs/base/common/uint';
-export class PrefixSumIndexOfResult {
- _prefixSumIndexOfResultBrand: void = undefined;
-
- index: number;
- remainder: number;
-
- constructor(index: number, remainder: number) {
- this.index = index;
- this.remainder = remainder;
- }
-}
-
export class PrefixSumComputer {
/**
@@ -71,7 +60,7 @@ export class PrefixSumComputer {
return true;
}
- public changeValue(index: number, value: number): boolean {
+ public setValue(index: number, value: number): boolean {
index = toUint32(index);
value = toUint32(value);
@@ -187,3 +176,119 @@ export class PrefixSumComputer {
return new PrefixSumIndexOfResult(mid, sum - midStart);
}
}
+
+/**
+ * {@link getIndexOf} has an amortized runtime complexity of O(1).
+ *
+ * ({@link PrefixSumComputer.getIndexOf} is just O(log n))
+*/
+export class ConstantTimePrefixSumComputer {
+ private _values: number[];
+ private _isValid: boolean;
+ private _validEndIndex: number;
+
+ /**
+ * _prefixSum[i] = SUM(values[j]), 0 <= j <= i
+ */
+ private _prefixSum: number[];
+
+ /**
+ * _indexBySum[sum] = idx => _prefixSum[idx - 1] <= sum < _prefixSum[idx]
+ */
+ private _indexBySum: number[];
+
+ constructor(values: number[]) {
+ this._values = values;
+ this._isValid = false;
+ this._validEndIndex = -1;
+ this._prefixSum = [];
+ this._indexBySum = [];
+ }
+
+ /**
+ * @returns SUM(0 <= j < values.length, values[j])
+ */
+ public getTotalSum(): number {
+ this._ensureValid();
+ return this._indexBySum.length;
+ }
+
+ /**
+ * @returns `SUM(0 <= j <= index, values[j])`. Includes `values[index]`!
+ */
+ public getPrefixSum(index: number): number {
+ this._ensureValid();
+ return this._prefixSum[index];
+ }
+
+ /**
+ * @returns `result`, such that `getPrefixSum(result.index - 1) + result.remainder = sum`
+ */
+ public getIndexOf(sum: number): PrefixSumIndexOfResult {
+ this._ensureValid();
+ const idx = this._indexBySum[sum];
+ const viewLinesAbove = idx > 0 ? this._prefixSum[idx - 1] : 0;
+ return new PrefixSumIndexOfResult(idx, sum - viewLinesAbove);
+ }
+
+ public removeValues(start: number, deleteCount: number): void {
+ this._values.splice(start, deleteCount);
+ this._invalidate(start);
+ }
+
+ public insertValues(insertIndex: number, insertArr: number[]): void {
+ this._values = arrayInsert(this._values, insertIndex, insertArr);
+ this._invalidate(insertIndex);
+ }
+
+ private _invalidate(index: number): void {
+ this._isValid = false;
+ this._validEndIndex = Math.min(this._validEndIndex, index - 1);
+ }
+
+ private _ensureValid(): void {
+ if (this._isValid) {
+ return;
+ }
+
+ for (let i = this._validEndIndex + 1, len = this._values.length; i < len; i++) {
+ const value = this._values[i];
+ const sumAbove = i > 0 ? this._prefixSum[i - 1] : 0;
+
+ this._prefixSum[i] = sumAbove + value;
+ for (let j = 0; j < value; j++) {
+ this._indexBySum[sumAbove + j] = i;
+ }
+ }
+
+ // trim things
+ this._prefixSum.length = this._values.length;
+ this._indexBySum.length = this._prefixSum[this._prefixSum.length - 1];
+
+ // mark as valid
+ this._isValid = true;
+ this._validEndIndex = this._values.length - 1;
+ }
+
+ public setValue(index: number, value: number): void {
+ if (this._values[index] === value) {
+ // no change
+ return;
+ }
+ this._values[index] = value;
+ this._invalidate(index);
+ }
+}
+
+
+export class PrefixSumIndexOfResult {
+ _prefixSumIndexOfResultBrand: void = undefined;
+
+ constructor(
+ public readonly index: number,
+ public readonly remainder: number
+ ) {
+ this.index = index;
+ this.remainder = remainder;
+ }
+}
diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts
index 6fb158d78a3..7b998c5a808 100644
--- a/src/vs/editor/common/viewModel/splitLinesCollection.ts
+++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts
@@ -11,11 +11,11 @@ import { IRange, Range } from 'vs/editor/common/core/range';
import { BracketGuideOptions, EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDeltaDecoration, IndentGuide, IndentGuideHorizontalLine, ITextModel, PositionAffinity } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
-import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { LineInjectedText } from 'vs/editor/common/model/textModelEvents';
+import { ConstantTimePrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
export interface ILineBreaksComputerFactory {
createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer;
@@ -30,9 +30,9 @@ export interface ISimpleModel {
getValueInRange(range: IRange, eol?: EndOfLinePreference): string;
}
-export interface ISplitLine {
+export interface IModelLineProjection {
isVisible(): boolean;
- setVisible(isVisible: boolean): ISplitLine;
+ setVisible(isVisible: boolean): IModelLineProjection;
getLineBreakData(): LineBreakData | null;
getViewLineCount(): number;
@@ -144,89 +144,6 @@ const enum IndentGuideRepeatOption {
BlockAll = 2
}
-class LineNumberMapper {
-
- private _counts: number[];
- private _isValid: boolean;
- private _validEndIndex: number;
-
- private _modelToView: number[];
- private _viewToModel: number[];
-
- constructor(viewLineCounts: number[]) {
- this._counts = viewLineCounts;
- this._isValid = false;
- this._validEndIndex = -1;
- this._modelToView = [];
- this._viewToModel = [];
- }
-
- private _invalidate(index: number): void {
- this._isValid = false;
- this._validEndIndex = Math.min(this._validEndIndex, index - 1);
- }
-
- private _ensureValid(): void {
- if (this._isValid) {
- return;
- }
-
- for (let i = this._validEndIndex + 1, len = this._counts.length; i < len; i++) {
- const viewLineCount = this._counts[i];
- const viewLinesAbove = (i > 0 ? this._modelToView[i - 1] : 0);
-
- this._modelToView[i] = viewLinesAbove + viewLineCount;
- for (let j = 0; j < viewLineCount; j++) {
- this._viewToModel[viewLinesAbove + j] = i;
- }
- }
-
- // trim things
- this._modelToView.length = this._counts.length;
- this._viewToModel.length = this._modelToView[this._modelToView.length - 1];
-
- // mark as valid
- this._isValid = true;
- this._validEndIndex = this._counts.length - 1;
- }
-
- public changeValue(index: number, value: number): void {
- if (this._counts[index] === value) {
- // no change
- return;
- }
- this._counts[index] = value;
- this._invalidate(index);
- }
-
- public removeValues(start: number, deleteCount: number): void {
- this._counts.splice(start, deleteCount);
- this._invalidate(start);
- }
-
- public insertValues(insertIndex: number, insertArr: number[]): void {
- this._counts = arrays.arrayInsert(this._counts, insertIndex, insertArr);
- this._invalidate(insertIndex);
- }
-
- public getTotalValue(): number {
- this._ensureValid();
- return this._viewToModel.length;
- }
-
- public getAccumulatedValue(index: number): number {
- this._ensureValid();
- return this._modelToView[index];
- }
-
- public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
- this._ensureValid();
- const modelLineIndex = this._viewToModel[accumulatedValue];
- const viewLinesAbove = (modelLineIndex > 0 ? this._modelToView[modelLineIndex - 1] : 0);
- return new PrefixSumIndexOfResult(modelLineIndex, accumulatedValue - viewLinesAbove);
- }
-}
-
export class SplitLinesCollection implements IViewModelLinesCollection {
private readonly _editorId: number;
@@ -241,9 +158,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
private wrappingColumn: number;
private wrappingIndent: WrappingIndent;
private wrappingStrategy: 'simple' | 'advanced';
- private lines!: ISplitLine[];
- private prefixSumComputer!: LineNumberMapper;
+ private modelLineProjections!: IModelLineProjection[];
+
+ /**
+ * Reflects the sum of the line counts of all .
+ */
+ private projectedModelLineLineCounts!: ConstantTimePrefixSumComputer;
private hiddenAreasIds!: string[];
@@ -281,7 +202,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
private _constructLines(resetHiddenAreas: boolean, previousLineBreaks: ((LineBreakData | null)[]) | null): void {
- this.lines = [];
+ this.modelLineProjections = [];
if (resetHiddenAreas) {
this.hiddenAreasIds = [];
@@ -317,14 +238,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd);
- let line = createSplitLine(linesBreaks[i], !isInHiddenArea);
+ let line = createModelLineProjection(linesBreaks[i], !isInHiddenArea);
values[i] = line.getViewLineCount();
- this.lines[i] = line;
+ this.modelLineProjections[i] = line;
}
this._validModelVersionId = this.model.getVersionId();
- this.prefixSumComputer = new LineNumberMapper(values);
+ this.projectedModelLineLineCounts = new ConstantTimePrefixSumComputer(values);
}
public getHiddenAreas(): Range[] {
@@ -392,37 +313,37 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let hiddenAreas = newRanges;
let hiddenAreaStart = 1, hiddenAreaEnd = 0;
let hiddenAreaIdx = -1;
- let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.lines.length + 2;
+ let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.modelLineProjections.length + 2;
let hasVisibleLine = false;
- for (let i = 0; i < this.lines.length; i++) {
+ for (let i = 0; i < this.modelLineProjections.length; i++) {
let lineNumber = i + 1;
if (lineNumber === nextLineNumberToUpdateHiddenArea) {
hiddenAreaIdx++;
hiddenAreaStart = hiddenAreas[hiddenAreaIdx].startLineNumber;
hiddenAreaEnd = hiddenAreas[hiddenAreaIdx].endLineNumber;
- nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.lines.length + 2;
+ nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.modelLineProjections.length + 2;
}
let lineChanged = false;
if (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd) {
// Line should be hidden
- if (this.lines[i].isVisible()) {
- this.lines[i] = this.lines[i].setVisible(false);
+ if (this.modelLineProjections[i].isVisible()) {
+ this.modelLineProjections[i] = this.modelLineProjections[i].setVisible(false);
lineChanged = true;
}
} else {
hasVisibleLine = true;
// Line should be visible
- if (!this.lines[i].isVisible()) {
- this.lines[i] = this.lines[i].setVisible(true);
+ if (!this.modelLineProjections[i].isVisible()) {
+ this.modelLineProjections[i] = this.modelLineProjections[i].setVisible(true);
lineChanged = true;
}
}
if (lineChanged) {
- let newOutputLineCount = this.lines[i].getViewLineCount();
- this.prefixSumComputer.changeValue(i, newOutputLineCount);
+ let newOutputLineCount = this.modelLineProjections[i].getViewLineCount();
+ this.projectedModelLineLineCounts.setValue(i, newOutputLineCount);
}
}
@@ -435,19 +356,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public modelPositionIsVisible(modelLineNumber: number, _modelColumn: number): boolean {
- if (modelLineNumber < 1 || modelLineNumber > this.lines.length) {
+ if (modelLineNumber < 1 || modelLineNumber > this.modelLineProjections.length) {
// invalid arguments
return false;
}
- return this.lines[modelLineNumber - 1].isVisible();
+ return this.modelLineProjections[modelLineNumber - 1].isVisible();
}
public getModelLineViewLineCount(modelLineNumber: number): number {
- if (modelLineNumber < 1 || modelLineNumber > this.lines.length) {
+ if (modelLineNumber < 1 || modelLineNumber > this.modelLineProjections.length) {
// invalid arguments
return 1;
}
- return this.lines[modelLineNumber - 1].getViewLineCount();
+ return this.modelLineProjections[modelLineNumber - 1].getViewLineCount();
}
public setTabSize(newTabSize: number): boolean {
@@ -480,8 +401,8 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let previousLineBreaks: ((LineBreakData | null)[]) | null = null;
if (onlyWrappingColumnChanged) {
previousLineBreaks = [];
- for (let i = 0, len = this.lines.length; i < len; i++) {
- previousLineBreaks[i] = this.lines[i].getLineBreakData();
+ for (let i = 0, len = this.modelLineProjections.length; i < len; i++) {
+ previousLineBreaks[i] = this.modelLineProjections[i].getLineBreakData();
}
}
@@ -510,11 +431,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return null;
}
- let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1);
- let outputToLineNumber = this.prefixSumComputer.getAccumulatedValue(toLineNumber - 1);
+ let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(fromLineNumber - 2) + 1);
+ let outputToLineNumber = this.projectedModelLineLineCounts.getPrefixSum(toLineNumber - 1);
- this.lines.splice(fromLineNumber - 1, toLineNumber - fromLineNumber + 1);
- this.prefixSumComputer.removeValues(fromLineNumber - 1, toLineNumber - fromLineNumber + 1);
+ this.modelLineProjections.splice(fromLineNumber - 1, toLineNumber - fromLineNumber + 1);
+ this.projectedModelLineLineCounts.removeValues(fromLineNumber - 1, toLineNumber - fromLineNumber + 1);
return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber);
}
@@ -527,16 +448,16 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
// cannot use this.getHiddenAreas() because those decorations have already seen the effect of this model change
- const isInHiddenArea = (fromLineNumber > 2 && !this.lines[fromLineNumber - 2].isVisible());
+ const isInHiddenArea = (fromLineNumber > 2 && !this.modelLineProjections[fromLineNumber - 2].isVisible());
- let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1);
+ let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(fromLineNumber - 2) + 1);
let totalOutputLineCount = 0;
- let insertLines: ISplitLine[] = [];
+ let insertLines: IModelLineProjection[] = [];
let insertPrefixSumValues: number[] = [];
for (let i = 0, len = lineBreaks.length; i < len; i++) {
- let line = createSplitLine(lineBreaks[i], !isInHiddenArea);
+ let line = createModelLineProjection(lineBreaks[i], !isInHiddenArea);
insertLines.push(line);
let outputLineCount = line.getViewLineCount();
@@ -545,9 +466,9 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
// TODO@Alex: use arrays.arrayInsert
- this.lines = this.lines.slice(0, fromLineNumber - 1).concat(insertLines).concat(this.lines.slice(fromLineNumber - 1));
+ this.modelLineProjections = this.modelLineProjections.slice(0, fromLineNumber - 1).concat(insertLines).concat(this.modelLineProjections.slice(fromLineNumber - 1));
- this.prefixSumComputer.insertValues(fromLineNumber - 1, insertPrefixSumValues);
+ this.projectedModelLineLineCounts.insertValues(fromLineNumber - 1, insertPrefixSumValues);
return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1);
}
@@ -561,11 +482,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let lineIndex = lineNumber - 1;
- let oldOutputLineCount = this.lines[lineIndex].getViewLineCount();
- let isVisible = this.lines[lineIndex].isVisible();
- let line = createSplitLine(lineBreakData, isVisible);
- this.lines[lineIndex] = line;
- let newOutputLineCount = this.lines[lineIndex].getViewLineCount();
+ let oldOutputLineCount = this.modelLineProjections[lineIndex].getViewLineCount();
+ let isVisible = this.modelLineProjections[lineIndex].isVisible();
+ let line = createModelLineProjection(lineBreakData, isVisible);
+ this.modelLineProjections[lineIndex] = line;
+ let newOutputLineCount = this.modelLineProjections[lineIndex].getViewLineCount();
let lineMappingChanged = false;
let changeFrom = 0;
@@ -576,23 +497,23 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let deleteTo = -1;
if (oldOutputLineCount > newOutputLineCount) {
- changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(lineNumber - 2) + 1);
+ changeFrom = (lineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 2) + 1);
changeTo = changeFrom + newOutputLineCount - 1;
deleteFrom = changeTo + 1;
deleteTo = deleteFrom + (oldOutputLineCount - newOutputLineCount) - 1;
lineMappingChanged = true;
} else if (oldOutputLineCount < newOutputLineCount) {
- changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(lineNumber - 2) + 1);
+ changeFrom = (lineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 2) + 1);
changeTo = changeFrom + oldOutputLineCount - 1;
insertFrom = changeTo + 1;
insertTo = insertFrom + (newOutputLineCount - oldOutputLineCount) - 1;
lineMappingChanged = true;
} else {
- changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(lineNumber - 2) + 1);
+ changeFrom = (lineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 2) + 1);
changeTo = changeFrom + newOutputLineCount - 1;
}
- this.prefixSumComputer.changeValue(lineIndex, newOutputLineCount);
+ this.projectedModelLineLineCounts.setValue(lineIndex, newOutputLineCount);
const viewLinesChangedEvent = (changeFrom <= changeTo ? new viewEvents.ViewLinesChangedEvent(changeFrom, changeTo) : null);
const viewLinesInsertedEvent = (insertFrom <= insertTo ? new viewEvents.ViewLinesInsertedEvent(insertFrom, insertTo) : null);
@@ -603,14 +524,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
public acceptVersionId(versionId: number): void {
this._validModelVersionId = versionId;
- if (this.lines.length === 1 && !this.lines[0].isVisible()) {
+ if (this.modelLineProjections.length === 1 && !this.modelLineProjections[0].isVisible()) {
// At least one line must be visible => reset hidden areas
this.setHiddenAreas([]);
}
}
public getViewLineCount(): number {
- return this.prefixSumComputer.getTotalValue();
+ return this.projectedModelLineLineCounts.getTotalSum();
}
private _toValidViewLineNumber(viewLineNumber: number): number {
@@ -645,16 +566,16 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
// #region ViewLineInfo
- public getViewLineInfo(viewLineNumber: number): ViewLineInfo {
+ private getViewLineInfo(viewLineNumber: number): ViewLineInfo {
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
+ let r = this.projectedModelLineLineCounts.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
let remainder = r.remainder;
return new ViewLineInfo(lineIndex + 1, remainder);
}
private getMinColumnOfViewLine(viewLineInfo: ViewLineInfo): number {
- return this.lines[viewLineInfo.modelLineNumber - 1].getViewLineMinColumn(
+ return this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewLineMinColumn(
this.model,
viewLineInfo.modelLineNumber,
viewLineInfo.modelLineWrappedLineIdx
@@ -662,7 +583,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
private getModelStartPositionOfViewLine(viewLineInfo: ViewLineInfo): Position {
- const line = this.lines[viewLineInfo.modelLineNumber - 1];
+ const line = this.modelLineProjections[viewLineInfo.modelLineNumber - 1];
const minViewColumn = line.getViewLineMinColumn(
this.model,
viewLineInfo.modelLineNumber,
@@ -676,7 +597,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
private getModelEndPositionOfViewLine(viewLineInfo: ViewLineInfo): Position {
- const line = this.lines[viewLineInfo.modelLineNumber - 1];
+ const line = this.modelLineProjections[viewLineInfo.modelLineNumber - 1];
const maxViewColumn = line.getViewLineMaxColumn(
this.model,
viewLineInfo.modelLineNumber,
@@ -698,7 +619,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let viewLines = new Array();
for (let curModelLine = startViewLine.modelLineNumber; curModelLine <= endViewLine.modelLineNumber; curModelLine++) {
- const line = this.lines[curModelLine - 1];
+ const line = this.modelLineProjections[curModelLine - 1];
if (line.isVisible()) {
let startOffset =
@@ -794,7 +715,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let reqStart: Position | null = null;
for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) {
- const line = this.lines[modelLineIndex];
+ const line = this.modelLineProjections[modelLineIndex];
if (line.isVisible()) {
let viewLineStartIndex = line.getViewLineNumberOfModelPosition(0, modelLineIndex === modelStartLineIndex ? modelStart.column : 1);
let viewLineEndIndex = line.getViewLineNumberOfModelPosition(0, this.model.getLineMaxColumn(modelLineIndex + 1));
@@ -850,48 +771,28 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineContent(viewLineNumber: number): string {
- viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- let lineIndex = r.index;
- let remainder = r.remainder;
-
- return this.lines[lineIndex].getViewLineContent(this.model, lineIndex + 1, remainder);
+ const info = this.getViewLineInfo(viewLineNumber);
+ return this.modelLineProjections[info.modelLineNumber - 1].getViewLineContent(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
}
public getViewLineLength(viewLineNumber: number): number {
- viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- let lineIndex = r.index;
- let remainder = r.remainder;
-
- return this.lines[lineIndex].getViewLineLength(this.model, lineIndex + 1, remainder);
+ const info = this.getViewLineInfo(viewLineNumber);
+ return this.modelLineProjections[info.modelLineNumber - 1].getViewLineLength(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
}
public getViewLineMinColumn(viewLineNumber: number): number {
- viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- let lineIndex = r.index;
- let remainder = r.remainder;
-
- return this.lines[lineIndex].getViewLineMinColumn(this.model, lineIndex + 1, remainder);
+ const info = this.getViewLineInfo(viewLineNumber);
+ return this.modelLineProjections[info.modelLineNumber - 1].getViewLineMinColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
}
public getViewLineMaxColumn(viewLineNumber: number): number {
- viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- let lineIndex = r.index;
- let remainder = r.remainder;
-
- return this.lines[lineIndex].getViewLineMaxColumn(this.model, lineIndex + 1, remainder);
+ const info = this.getViewLineInfo(viewLineNumber);
+ return this.modelLineProjections[info.modelLineNumber - 1].getViewLineMaxColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
}
public getViewLineData(viewLineNumber: number): ViewLineData {
- viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- let lineIndex = r.index;
- let remainder = r.remainder;
-
- return this.lines[lineIndex].getViewLineData(this.model, lineIndex + 1, remainder);
+ const info = this.getViewLineInfo(viewLineNumber);
+ return this.modelLineProjections[info.modelLineNumber - 1].getViewLineData(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
}
public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] {
@@ -899,14 +800,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
- let start = this.prefixSumComputer.getIndexOf(viewStartLineNumber - 1);
+ let start = this.projectedModelLineLineCounts.getIndexOf(viewStartLineNumber - 1);
let viewLineNumber = viewStartLineNumber;
let startModelLineIndex = start.index;
let startRemainder = start.remainder;
let result: ViewLineData[] = [];
for (let modelLineIndex = startModelLineIndex, len = this.model.getLineCount(); modelLineIndex < len; modelLineIndex++) {
- let line = this.lines[modelLineIndex];
+ let line = this.modelLineProjections[modelLineIndex];
if (!line.isVisible()) {
continue;
}
@@ -935,11 +836,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position {
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
+ let r = this.projectedModelLineLineCounts.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
let remainder = r.remainder;
- let line = this.lines[lineIndex];
+ let line = this.modelLineProjections[lineIndex];
let minColumn = line.getViewLineMinColumn(this.model, lineIndex + 1, remainder);
let maxColumn = line.getViewLineMaxColumn(this.model, lineIndex + 1, remainder);
@@ -967,15 +868,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position {
- viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
+ const info = this.getViewLineInfo(viewLineNumber);
- let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- let lineIndex = r.index;
- let remainder = r.remainder;
-
- let inputColumn = this.lines[lineIndex].getModelColumnOfViewPosition(remainder, viewColumn);
+ let inputColumn = this.modelLineProjections[info.modelLineNumber - 1].getModelColumnOfViewPosition(info.modelLineWrappedLineIdx, viewColumn);
// console.log('out -> in ' + viewLineNumber + ',' + viewColumn + ' ===> ' + (lineIndex+1) + ',' + inputColumn);
- return this.model.validatePosition(new Position(lineIndex + 1, inputColumn));
+ return this.model.validatePosition(new Position(info.modelLineNumber, inputColumn));
}
public convertViewRangeToModelRange(viewRange: Range): Range {
@@ -991,22 +888,22 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
const inputColumn = validPosition.column;
let lineIndex = inputLineNumber - 1, lineIndexChanged = false;
- while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) {
+ while (lineIndex > 0 && !this.modelLineProjections[lineIndex].isVisible()) {
lineIndex--;
lineIndexChanged = true;
}
- if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) {
+ if (lineIndex === 0 && !this.modelLineProjections[lineIndex].isVisible()) {
// Could not reach a real line
// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1);
return new Position(1, 1);
}
- const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
+ const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.projectedModelLineLineCounts.getPrefixSum(lineIndex - 1));
let r: Position;
if (lineIndexChanged) {
- r = this.lines[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1), affinity);
+ r = this.modelLineProjections[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1), affinity);
} else {
- r = this.lines[inputLineNumber - 1].getViewPositionOfModelPosition(deltaLineNumber, inputColumn, affinity);
+ r = this.modelLineProjections[inputLineNumber - 1].getViewPositionOfModelPosition(deltaLineNumber, inputColumn, affinity);
}
// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r);
@@ -1027,24 +924,24 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
}
- public getViewLineNumberOfModelPosition(inputLineNumber: number, inputColumn: number): number {
- let lineIndex = inputLineNumber - 1;
- if (this.lines[lineIndex].isVisible()) {
+ public getViewLineNumberOfModelPosition(modelLineNumber: number, modelColumn: number): number {
+ let lineIndex = modelLineNumber - 1;
+ if (this.modelLineProjections[lineIndex].isVisible()) {
// this model line is visible
- const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
- return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, inputColumn);
+ const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.projectedModelLineLineCounts.getPrefixSum(lineIndex - 1));
+ return this.modelLineProjections[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, modelColumn);
}
// this model line is not visible
- while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) {
+ while (lineIndex > 0 && !this.modelLineProjections[lineIndex].isVisible()) {
lineIndex--;
}
- if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) {
+ if (lineIndex === 0 && !this.modelLineProjections[lineIndex].isVisible()) {
// Could not reach a real line
return 1;
}
- const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
- return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1));
+ const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.projectedModelLineLineCounts.getPrefixSum(lineIndex - 1));
+ return this.modelLineProjections[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1));
}
public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[] {
@@ -1063,7 +960,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let reqStart: Position | null = null;
for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) {
- const line = this.lines[modelLineIndex];
+ const line = this.modelLineProjections[modelLineIndex];
if (line.isVisible()) {
// merge into previous request
if (reqStart === null) {
@@ -1115,31 +1012,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getInjectedTextAt(position: Position): InjectedText | null {
- const viewLineNumber = this._toValidViewLineNumber(position.lineNumber);
- const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- const lineIndex = r.index;
- const remainder = r.remainder;
-
- return this.lines[lineIndex].getInjectedTextAt(remainder, position.column);
+ const info = this.getViewLineInfo(position.lineNumber);
+ return this.modelLineProjections[info.modelLineNumber - 1].getInjectedTextAt(info.modelLineWrappedLineIdx, position.column);
}
normalizePosition(position: Position, affinity: PositionAffinity): Position {
- const viewLineNumber = this._toValidViewLineNumber(position.lineNumber);
- const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- const lineIndex = r.index;
- const remainder = r.remainder;
-
- return this.lines[lineIndex].normalizePosition(this.model, lineIndex + 1, remainder, position, affinity);
+ const info = this.getViewLineInfo(position.lineNumber);
+ return this.modelLineProjections[info.modelLineNumber - 1].normalizePosition(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx, position, affinity);
}
public getLineIndentColumn(lineNumber: number): number {
- const viewLineNumber = this._toValidViewLineNumber(lineNumber);
- const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
- const lineIndex = r.index;
- const remainder = r.remainder;
-
- if (remainder === 0) {
- return this.model.getLineIndentColumn(lineIndex + 1);
+ const info = this.getViewLineInfo(lineNumber);
+ if (info.modelLineWrappedLineIdx === 0) {
+ return this.model.getLineIndentColumn(info.modelLineNumber);
}
// wrapped lines have no indentation.
@@ -1171,9 +1056,12 @@ class ViewLineInfoGroupedByModelRange {
}
}
-class VisibleIdentitySplitLine implements ISplitLine {
+/**
+ * This projection does not change the model line.
+*/
+class IdentityModelLineProjection implements IModelLineProjection {
- public static readonly INSTANCE = new VisibleIdentitySplitLine();
+ public static readonly INSTANCE = new IdentityModelLineProjection();
private constructor() { }
@@ -1181,11 +1069,11 @@ class VisibleIdentitySplitLine implements ISplitLine {
return true;
}
- public setVisible(isVisible: boolean): ISplitLine {
+ public setVisible(isVisible: boolean): IModelLineProjection {
if (isVisible) {
return this;
}
- return InvisibleIdentitySplitLine.INSTANCE;
+ return HiddenModelLineProjection.INSTANCE;
}
public getLineBreakData(): LineBreakData | null {
@@ -1255,9 +1143,12 @@ class VisibleIdentitySplitLine implements ISplitLine {
}
}
-class InvisibleIdentitySplitLine implements ISplitLine {
+/**
+ * This projection hides the model line.
+ */
+class HiddenModelLineProjection implements IModelLineProjection {
- public static readonly INSTANCE = new InvisibleIdentitySplitLine();
+ public static readonly INSTANCE = new HiddenModelLineProjection();
private constructor() { }
@@ -1265,11 +1156,11 @@ class InvisibleIdentitySplitLine implements ISplitLine {
return false;
}
- public setVisible(isVisible: boolean): ISplitLine {
+ public setVisible(isVisible: boolean): IModelLineProjection {
if (!isVisible) {
return this;
}
- return VisibleIdentitySplitLine.INSTANCE;
+ return IdentityModelLineProjection.INSTANCE;
}
public getLineBreakData(): LineBreakData | null {
@@ -1325,7 +1216,12 @@ class InvisibleIdentitySplitLine implements ISplitLine {
}
}
-export class SplitLine implements ISplitLine {
+/**
+ * This projection is used to
+ * * wrap model lines
+ * * inject text
+ */
+export class ModelLineProjection implements IModelLineProjection {
private readonly _lineBreakData: LineBreakData;
private _isVisible: boolean;
@@ -1339,7 +1235,7 @@ export class SplitLine implements ISplitLine {
return this._isVisible;
}
- public setVisible(isVisible: boolean): ISplitLine {
+ public setVisible(isVisible: boolean): IModelLineProjection {
this._isVisible = isVisible;
return this;
}
@@ -1626,15 +1522,15 @@ function _makeSpaces(count: number): string {
return new Array(count + 1).join(' ');
}
-function createSplitLine(lineBreakData: LineBreakData | null, isVisible: boolean): ISplitLine {
+function createModelLineProjection(lineBreakData: LineBreakData | null, isVisible: boolean): IModelLineProjection {
if (lineBreakData === null) {
// No mapping needed
if (isVisible) {
- return VisibleIdentitySplitLine.INSTANCE;
+ return IdentityModelLineProjection.INSTANCE;
}
- return InvisibleIdentitySplitLine.INSTANCE;
+ return HiddenModelLineProjection.INSTANCE;
} else {
- return new SplitLine(lineBreakData, isVisible);
+ return new ModelLineProjection(lineBreakData, isVisible);
}
}
diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts
index 955bfa89dfd..7dbc428c99e 100644
--- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts
+++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts
@@ -181,7 +181,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont
const position = selection.getStartPosition();
// find matching brackets if position is on a bracket
- const brackets = model.matchBracket(position);
+ const brackets = model.bracketPairs.matchBracket(position);
let newCursorPosition: Position | null = null;
if (brackets) {
if (brackets[0].containsPosition(position)) {
@@ -191,12 +191,12 @@ export class BracketMatchingController extends Disposable implements IEditorCont
}
} else {
// find the enclosing brackets if the position isn't on a matching bracket
- const enclosingBrackets = model.findEnclosingBrackets(position);
+ const enclosingBrackets = model.bracketPairs.findEnclosingBrackets(position);
if (enclosingBrackets) {
newCursorPosition = enclosingBrackets[0].getStartPosition();
} else {
// no enclosing brackets, try the very first next bracket
- const nextBracket = model.findNextBracket(position);
+ const nextBracket = model.bracketPairs.findNextBracket(position);
if (nextBracket && nextBracket.range) {
newCursorPosition = nextBracket.range.getStartPosition();
}
@@ -223,14 +223,14 @@ export class BracketMatchingController extends Disposable implements IEditorCont
this._editor.getSelections().forEach(selection => {
const position = selection.getStartPosition();
- let brackets = model.matchBracket(position);
+ let brackets = model.bracketPairs.matchBracket(position);
if (!brackets) {
- brackets = model.findEnclosingBrackets(position);
+ brackets = model.bracketPairs.findEnclosingBrackets(position);
if (!brackets) {
- const nextBracket = model.findNextBracket(position);
+ const nextBracket = model.bracketPairs.findNextBracket(position);
if (nextBracket && nextBracket.range) {
- brackets = model.matchBracket(nextBracket.range.getStartPosition());
+ brackets = model.bracketPairs.matchBracket(nextBracket.range.getStartPosition());
}
}
}
@@ -348,10 +348,10 @@ export class BracketMatchingController extends Disposable implements IEditorCont
if (previousIndex < previousLen && previousData[previousIndex].position.equals(position)) {
newData[newDataLen++] = previousData[previousIndex];
} else {
- let brackets = model.matchBracket(position);
+ let brackets = model.bracketPairs.matchBracket(position);
let options = BracketMatchingController._DECORATION_OPTIONS_WITH_OVERVIEW_RULER;
if (!brackets && this._matchBrackets === 'always') {
- brackets = model.findEnclosingBrackets(position, 20 /* give at most 20ms to compute */);
+ brackets = model.bracketPairs.findEnclosingBrackets(position, 20 /* give at most 20ms to compute */);
options = BracketMatchingController._DECORATION_OPTIONS_WITHOUT_OVERVIEW_RULER;
}
newData[newDataLen++] = new BracketsData(position, brackets, options);
diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts
index 85d889c652f..6c21e05e257 100644
--- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts
+++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts
@@ -52,6 +52,8 @@ export class ColorPickerHeader extends Disposable {
this._register(model.onDidChangePresentation(this.onDidChangePresentation, this));
this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(model.color) || '';
this.pickedColorNode.classList.toggle('light', model.color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : model.color.isLighter());
+
+ this.onDidChangeColor(this.model.color);
}
private onDidChangeColor(color: Color): void {
diff --git a/src/vs/editor/contrib/gotoError/markerNavigationService.ts b/src/vs/editor/contrib/gotoError/markerNavigationService.ts
index 04a59413ed3..d9213a66ce6 100644
--- a/src/vs/editor/contrib/gotoError/markerNavigationService.ts
+++ b/src/vs/editor/contrib/gotoError/markerNavigationService.ts
@@ -15,6 +15,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class MarkerCoordinate {
constructor(
@@ -38,6 +39,7 @@ export class MarkerList {
constructor(
resourceFilter: URI | ((uri: URI) => boolean) | undefined,
@IMarkerService private readonly _markerService: IMarkerService,
+ @IConfigurationService private readonly _configService: IConfigurationService,
) {
if (URI.isUri(resourceFilter)) {
this._resourceFilter = uri => uri.toString() === resourceFilter.toString();
@@ -45,6 +47,17 @@ export class MarkerList {
this._resourceFilter = resourceFilter;
}
+ const compareOrder = this._configService.getValue('problems.compareOrder');
+ const compareMarker = (a: IMarker, b: IMarker): number => {
+ let res = compare(a.resource.toString(), b.resource.toString());
+ if (compareOrder === 'position') {
+ res = Range.compareRangesUsingStarts(a, b) || MarkerSeverity.compare(a.severity, b.severity);
+ } else {
+ res = MarkerSeverity.compare(a.severity, b.severity) || Range.compareRangesUsingStarts(a, b);
+ }
+ return res;
+ };
+
const updateMarker = () => {
this._markers = this._markerService.read({
resource: URI.isUri(resourceFilter) ? resourceFilter : undefined,
@@ -53,7 +66,7 @@ export class MarkerList {
if (typeof resourceFilter === 'function') {
this._markers = this._markers.filter(m => this._resourceFilter!(m.resource));
}
- this._markers.sort(MarkerList._compareMarker);
+ this._markers.sort(compareMarker);
};
updateMarker();
@@ -164,17 +177,6 @@ export class MarkerList {
}
return undefined;
}
-
- private static _compareMarker(a: IMarker, b: IMarker): number {
- let res = compare(a.resource.toString(), b.resource.toString());
- if (res === 0) {
- res = MarkerSeverity.compare(a.severity, b.severity);
- }
- if (res === 0) {
- res = Range.compareRangesUsingStarts(a, b);
- }
- return res;
- }
}
export const IMarkerNavigationService = createDecorator('IMarkerNavigationService');
@@ -195,7 +197,10 @@ class MarkerNavigationService implements IMarkerNavigationService, IMarkerListPr
private readonly _provider = new LinkedList();
- constructor(@IMarkerService private readonly _markerService: IMarkerService) { }
+ constructor(
+ @IMarkerService private readonly _markerService: IMarkerService,
+ @IConfigurationService private readonly _configService: IConfigurationService,
+ ) { }
registerProvider(provider: IMarkerListProvider): IDisposable {
const remove = this._provider.unshift(provider);
@@ -210,7 +215,7 @@ class MarkerNavigationService implements IMarkerNavigationService, IMarkerListPr
}
}
// default
- return new MarkerList(resource, this._markerService);
+ return new MarkerList(resource, this._markerService, this._configService);
}
}
diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts
index e7da40052c5..655395a7aba 100644
--- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts
+++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts
@@ -18,7 +18,8 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
-import { IFoundBracket, IModelDeltaDecoration, ITextModel, IWordAtPosition } from 'vs/editor/common/model';
+import { IModelDeltaDecoration, ITextModel, IWordAtPosition } from 'vs/editor/common/model';
+import { IFoundBracket } from 'vs/editor/common/model/bracketPairs/bracketPairs';
import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -260,7 +261,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri
const brackets: IFoundBracket[] = [];
let ignoreFirstEmpty = true;
- let currentBracket = textEditorModel.findNextBracket(new Position(startLineNumber, 1));
+ let currentBracket = textEditorModel.bracketPairs.findNextBracket(new Position(startLineNumber, 1));
while (currentBracket !== null) {
if (brackets.length === 0) {
@@ -294,7 +295,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri
return new Range(startLineNumber, 1, maxLineNumber + 1, 1);
}
- currentBracket = textEditorModel.findNextBracket(new Position(nextLineNumber, nextColumn));
+ currentBracket = textEditorModel.bracketPairs.findNextBracket(new Position(nextLineNumber, nextColumn));
}
return new Range(startLineNumber, 1, maxLineNumber + 1, 1);
diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts
index 84d0d7cec65..4fe8147dcb3 100644
--- a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts
+++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts
@@ -21,7 +21,7 @@ import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { ILanguageIdCodec } from 'vs/editor/common/modes';
import { IModeService } from 'vs/editor/common/services/modeService';
-import { ghostTextBorder, ghostTextForeground } from 'vs/editor/common/view/editorColorRegistry';
+import { ghostTextBackground, ghostTextBorder, ghostTextForeground } from 'vs/editor/common/view/editorColorRegistry';
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel';
@@ -226,6 +226,7 @@ class DecorationsWidget implements IDisposable {
const colorTheme = this.themeService.getColorTheme();
const foreground = colorTheme.getColor(ghostTextForeground);
+
let opacity: string | undefined = undefined;
let color: string | undefined = undefined;
if (foreground) {
@@ -499,15 +500,18 @@ class ViewMoreLinesContentWidget extends Disposable implements IContentWidget {
registerThemingParticipant((theme, collector) => {
const foreground = theme.getColor(ghostTextForeground);
-
if (foreground) {
- const opacity = String(foreground.rgba.a);
- const color = Color.Format.CSS.format(opaque(foreground))!;
-
// `!important` ensures that other decorations don't cause a style conflict (#132017).
- collector.addRule(`.monaco-editor .ghost-text-decoration { opacity: ${opacity} !important; color: ${color} !important; }`);
+ collector.addRule(`.monaco-editor .ghost-text-decoration { color: ${foreground.toString()} !important; }`);
collector.addRule(`.monaco-editor .ghost-text-decoration-preview { color: ${foreground.toString()} !important; }`);
- collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { opacity: ${opacity} !important; color: ${color} !important; }`);
+ collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { color: ${foreground.toString()} !important; }`);
+ }
+
+ const background = theme.getColor(ghostTextBackground);
+ if (background) {
+ collector.addRule(`.monaco-editor .ghost-text-decoration { background-color: ${background.toString()}; }`);
+ collector.addRule(`.monaco-editor .ghost-text-decoration-preview { background-color: ${background.toString()}; }`);
+ collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { background-color: ${background.toString()}; }`);
}
const border = theme.getColor(ghostTextBorder);
diff --git a/src/vs/editor/contrib/message/messageController.css b/src/vs/editor/contrib/message/messageController.css
index 924349d112e..fdd3476fbd3 100644
--- a/src/vs/editor/contrib/message/messageController.css
+++ b/src/vs/editor/contrib/message/messageController.css
@@ -32,6 +32,13 @@
.monaco-editor .monaco-editor-overlaymessage .message {
padding: 1px 4px;
+ color: var(--vscode-inputValidation-infoForeground);
+ background-color: var(--vscode-inputValidation-infoBackground);
+ border: 1px solid var(--vscode-inputValidation-infoBorder);
+}
+
+.monaco-editor.hc-black .monaco-editor-overlaymessage .message {
+ border-width: 2px;
}
.monaco-editor .monaco-editor-overlaymessage .anchor {
@@ -44,6 +51,14 @@
position: absolute;
}
+.monaco-editor .monaco-editor-overlaymessage .anchor.top {
+ border-bottom-color: var(--vscode-inputValidation-infoBorder);
+}
+
+.monaco-editor .monaco-editor-overlaymessage .anchor.below {
+ border-top-color: var(--vscode-inputValidation-infoBorder);
+}
+
.monaco-editor .monaco-editor-overlaymessage:not(.below) .anchor.top,
.monaco-editor .monaco-editor-overlaymessage.below .anchor.below {
display: none;
diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts
index 277309eaed7..5efbb4e9be0 100644
--- a/src/vs/editor/contrib/message/messageController.ts
+++ b/src/vs/editor/contrib/message/messageController.ts
@@ -16,9 +16,6 @@ import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
import * as nls from 'vs/nls';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
-import { inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground } from 'vs/platform/theme/common/colorRegistry';
-import { ColorScheme } from 'vs/platform/theme/common/theme';
-import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
export class MessageController implements IEditorContribution {
@@ -193,21 +190,3 @@ class MessageWidget implements IContentWidget {
}
registerEditorContribution(MessageController.ID, MessageController);
-
-registerThemingParticipant((theme, collector) => {
- const border = theme.getColor(inputValidationInfoBorder);
- if (border) {
- let borderWidth = theme.type === ColorScheme.HIGH_CONTRAST ? 2 : 1;
- collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .anchor.below { border-top-color: ${border}; }`);
- collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .anchor.top { border-bottom-color: ${border}; }`);
- collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .message { border: ${borderWidth}px solid ${border}; }`);
- }
- const background = theme.getColor(inputValidationInfoBackground);
- if (background) {
- collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .message { background-color: ${background}; }`);
- }
- const foreground = theme.getColor(inputValidationInfoForeground);
- if (foreground) {
- collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .message { color: ${foreground}; }`);
- }
-});
diff --git a/src/vs/editor/contrib/smartSelect/bracketSelections.ts b/src/vs/editor/contrib/smartSelect/bracketSelections.ts
index fc4e5e43337..716d830d06a 100644
--- a/src/vs/editor/contrib/smartSelect/bracketSelections.ts
+++ b/src/vs/editor/contrib/smartSelect/bracketSelections.ts
@@ -41,7 +41,7 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider {
resolve();
break;
}
- let bracket = model.findNextBracket(pos);
+ let bracket = model.bracketPairs.findNextBracket(pos);
if (!bracket) {
resolve();
break;
@@ -86,7 +86,7 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider {
resolve();
break;
}
- let bracket = model.findPrevBracket(pos);
+ let bracket = model.bracketPairs.findPrevBracket(pos);
if (!bracket) {
resolve();
break;
diff --git a/src/vs/editor/contrib/snippet/snippetSession.css b/src/vs/editor/contrib/snippet/snippetSession.css
index d262eaf1b11..0f52c3d5943 100644
--- a/src/vs/editor/contrib/snippet/snippetSession.css
+++ b/src/vs/editor/contrib/snippet/snippetSession.css
@@ -7,9 +7,13 @@
min-width: 2px;
outline-style: solid;
outline-width: 1px;
+ background-color: var(--vscode-editor-snippetTabstopHighlightBackground, transparent);
+ outline-color: var(--vscode-editor-snippetTabstopHighlightBorder, transparent);
}
.monaco-editor .finish-snippet-placeholder {
outline-style: solid;
outline-width: 1px;
+ background-color: var(--vscode-editor-snippetFinalTabstopHighlightBackground, transparent);
+ outline-color: var(--vscode-editor-snippetFinalTabstopHighlightBorder, transparent);
}
diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts
index 5b484ffb7bf..ff99f621a7a 100644
--- a/src/vs/editor/contrib/snippet/snippetSession.ts
+++ b/src/vs/editor/contrib/snippet/snippetSession.ts
@@ -19,23 +19,10 @@ import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness } fr
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { OvertypingCapturer } from 'vs/editor/contrib/suggest/suggestOvertypingCapturer';
import { ILabelService } from 'vs/platform/label/common/label';
-import * as colors from 'vs/platform/theme/common/colorRegistry';
-import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Choice, Marker, Placeholder, SnippetParser, Text, TextmateSnippet } from './snippetParser';
import { ClipboardBasedVariableResolver, CommentBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, RandomBasedVariableResolver, SelectionBasedVariableResolver, TimeBasedVariableResolver, WorkspaceBasedVariableResolver } from './snippetVariables';
-registerThemingParticipant((theme, collector) => {
-
- function getColorGraceful(name: string) {
- const color = theme.getColor(name);
- return color ? color.toString() : 'transparent';
- }
-
- collector.addRule(`.monaco-editor .snippet-placeholder { background-color: ${getColorGraceful(colors.snippetTabstopHighlightBackground)}; outline-color: ${getColorGraceful(colors.snippetTabstopHighlightBorder)}; }`);
- collector.addRule(`.monaco-editor .finish-snippet-placeholder { background-color: ${getColorGraceful(colors.snippetFinalTabstopHighlightBackground)}; outline-color: ${getColorGraceful(colors.snippetFinalTabstopHighlightBorder)}; }`);
-});
-
export class OneSnippet {
private _placeholderDecorations?: Map;
diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css
index 3d118cc8d09..ae438dbf51d 100644
--- a/src/vs/editor/contrib/suggest/media/suggest.css
+++ b/src/vs/editor/contrib/suggest/media/suggest.css
@@ -23,6 +23,8 @@
width: 100%;
border-style: solid;
border-width: 1px;
+ border-color: var(--vscode-editorSuggestWidget-border);
+ background-color: var(--vscode-editorSuggestWidget-background);
}
.monaco-editor.hc-black .suggest-widget,
@@ -41,7 +43,7 @@
width: 100%;
font-size: 80%;
padding: 0 4px 0 4px;
- border-top: 1px solid transparent;
+ border-top: 1px solid var(--vscode-editorSuggestWidget-border);
overflow: hidden;
}
@@ -109,6 +111,14 @@
touch-action: none;
}
+.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused {
+ color: var(--vscode-editorSuggestWidget-selectedForeground);
+}
+
+.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused .codicon {
+ color: var(--vscode-editorSuggestWidget-selectedIconForeground);
+}
+
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents {
flex: 1;
height: 100%;
@@ -132,6 +142,14 @@
font-weight: bold;
}
+.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight {
+ color: var(--vscode-editorSuggestWidget-highlightForeground);
+}
+
+.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused .monaco-highlighted-label .highlight {
+ color: var(--vscode-editorSuggestWidget-focusHighlightForeground);
+}
+
/** ReadMore Icon styles **/
.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.header>.codicon-close,
@@ -329,6 +347,23 @@
display: flex;
flex-direction: column;
cursor: default;
+ color: var(--vscode-editorSuggestWidget-foreground);
+}
+
+.monaco-editor .suggest-details.focused {
+ border-color: var(--vscode-focusBorder);
+}
+
+.monaco-editor .suggest-details a {
+ color: var(--vscode-textLink-foreground);
+}
+
+.monaco-editor .suggest-details a:hover {
+ color: var(--vscode-textLink-activeForeground);
+}
+
+.monaco-editor .suggest-details code {
+ background-color: var(--vscode-textCodeBlock-background);
}
.monaco-editor .suggest-details.no-docs {
diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts
index 8a7c2f559c3..6de2eb0900c 100644
--- a/src/vs/editor/contrib/suggest/suggestController.ts
+++ b/src/vs/editor/contrib/suggest/suggestController.ts
@@ -457,9 +457,9 @@ export class SuggestController implements IEditorContribution {
}
}
- triggerSuggest(onlyFrom?: Set): void {
+ triggerSuggest(onlyFrom?: Set, auto?: boolean): void {
if (this.editor.hasModel()) {
- this.model.trigger({ auto: false, shy: false }, false, onlyFrom);
+ this.model.trigger({ auto: auto ?? false, shy: false }, false, onlyFrom);
this.editor.revealPosition(this.editor.getPosition(), ScrollType.Smooth);
this.editor.focus();
}
@@ -662,14 +662,22 @@ export class TriggerSuggestAction extends EditorAction {
});
}
- public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
+ run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
const controller = SuggestController.get(editor);
if (!controller) {
return;
}
- controller.triggerSuggest();
+ type TriggerArgs = { auto: boolean };
+ let auto: boolean | undefined;
+ if (args && typeof args === 'object') {
+ if ((args).auto === true) {
+ auto = true;
+ }
+ }
+
+ controller.triggerSuggest(undefined, auto);
}
}
diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts
index ffff62a951a..f2bfb4cfe69 100644
--- a/src/vs/editor/contrib/suggest/suggestWidget.ts
+++ b/src/vs/editor/contrib/suggest/suggestWidget.ts
@@ -25,9 +25,9 @@ import * as nls from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
-import { activeContrastBorder, editorForeground, editorWidgetBackground, editorWidgetBorder, focusBorder, listFocusHighlightForeground, listHighlightForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, registerColor, textCodeBlockBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
+import { activeContrastBorder, editorForeground, editorWidgetBackground, editorWidgetBorder, listFocusHighlightForeground, listHighlightForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
import { attachListStyler } from 'vs/platform/theme/common/styler';
-import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
+import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { CompletionModel } from './completionModel';
import { ResizableHTMLElement } from './resizable';
import { CompletionItem, Context as SuggestContext } from './suggest';
@@ -140,9 +140,6 @@ export class SuggestWidget implements IDisposable {
private readonly _onDetailsKeydown = new Emitter();
readonly onDetailsKeyDown: Event = this._onDetailsKeydown.event;
- private _detailsFocusBorderColor?: string;
- private _detailsBorderColor?: string;
-
constructor(
private readonly editor: ICodeEditor,
@IStorageService private readonly _storageService: IStorageService,
@@ -336,24 +333,6 @@ export class SuggestWidget implements IDisposable {
}
private _onThemeChange(theme: IColorTheme) {
- const backgroundColor = theme.getColor(editorSuggestWidgetBackground);
- if (backgroundColor) {
- this.element.domNode.style.backgroundColor = backgroundColor.toString();
- this._messageElement.style.backgroundColor = backgroundColor.toString();
- this._details.widget.domNode.style.backgroundColor = backgroundColor.toString();
- }
- const borderColor = theme.getColor(editorSuggestWidgetBorder);
- if (borderColor) {
- this.element.domNode.style.borderColor = borderColor.toString();
- this._messageElement.style.borderColor = borderColor.toString();
- this._status.element.style.borderTopColor = borderColor.toString();
- this._details.widget.domNode.style.borderColor = borderColor.toString();
- this._detailsBorderColor = borderColor.toString();
- }
- const focusBorderColor = theme.getColor(focusBorder);
- if (focusBorderColor) {
- this._detailsFocusBorderColor = focusBorderColor.toString();
- }
this._details.widget.borderWidth = theme.type === 'hc' ? 2 : 1;
}
@@ -547,9 +526,7 @@ export class SuggestWidget implements IDisposable {
this._layout(this.element.size);
// Reset focus border
- if (this._detailsBorderColor) {
- this._details.widget.domNode.style.borderColor = this._detailsBorderColor;
- }
+ this._details.widget.domNode.classList.remove('focused');
}
selectNextPage(): boolean {
@@ -655,14 +632,11 @@ export class SuggestWidget implements IDisposable {
toggleDetailsFocus(): void {
if (this._state === State.Details) {
this._setState(State.Open);
- if (this._detailsBorderColor) {
- this._details.widget.domNode.style.borderColor = this._detailsBorderColor;
- }
+ this._details.widget.domNode.classList.remove('focused');
+
} else if (this._state === State.Open && this._isDetailsVisible()) {
this._setState(State.Details);
- if (this._detailsFocusBorderColor) {
- this._details.widget.domNode.style.borderColor = this._detailsFocusBorderColor;
- }
+ this._details.widget.domNode.classList.add('focused');
}
}
@@ -982,45 +956,3 @@ export class SuggestContentWidget implements IContentWidget {
this._position = position;
}
}
-
-registerThemingParticipant((theme, collector) => {
- const matchHighlight = theme.getColor(editorSuggestWidgetHighlightForeground);
- if (matchHighlight) {
- collector.addRule(`.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: ${matchHighlight}; }`);
- }
-
- const matchHighlightFocus = theme.getColor(editorSuggestWidgetHighlightFocusForeground);
- if (matchHighlight) {
- collector.addRule(`.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused .monaco-highlighted-label .highlight { color: ${matchHighlightFocus}; }`);
- }
-
- const foreground = theme.getColor(editorSuggestWidgetForeground);
- if (foreground) {
- collector.addRule(`.monaco-editor .suggest-widget, .monaco-editor .suggest-details { color: ${foreground}; }`);
- }
-
- const selectedForeground = theme.getColor(editorSuggestWidgetSelectedForeground);
- if (selectedForeground) {
- collector.addRule(`.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused { color: ${selectedForeground}; }`);
- }
-
- const selectedIconForeground = theme.getColor(editorSuggestWidgetSelectedIconForeground);
- if (selectedIconForeground) {
- collector.addRule(`.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused .codicon { color: ${selectedIconForeground}; }`);
- }
-
- const link = theme.getColor(textLinkForeground);
- if (link) {
- collector.addRule(`.monaco-editor .suggest-details a { color: ${link}; }`);
- }
-
- const linkHover = theme.getColor(textLinkActiveForeground);
- if (linkHover) {
- collector.addRule(`.monaco-editor .suggest-details a:hover { color: ${linkHover}; }`);
- }
-
- const codeBackground = theme.getColor(textCodeBlockBackground);
- if (codeBackground) {
- collector.addRule(`.monaco-editor .suggest-details code { background-color: ${codeBackground}; }`);
- }
-});
diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts
index ece509fa0d0..779f91ebf31 100644
--- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts
+++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts
@@ -12,7 +12,7 @@ import { BuiltinTheme, IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeS
import { hc_black, vs, vs_dark } from 'vs/editor/standalone/common/themes';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Registry } from 'vs/platform/registry/common/platform';
-import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry';
+import { asCssVariableName, ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry';
import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { ColorScheme } from 'vs/platform/theme/common/theme';
@@ -346,6 +346,15 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon
};
themingRegistry.getThemingParticipants().forEach(p => p(this._theme, ruleCollector, this._environment));
+ const colorVariables: string[] = [];
+ for (const item of colorRegistry.getColors()) {
+ const color = this._theme.getColor(item.id, true);
+ if (color) {
+ colorVariables.push(`${asCssVariableName(item.id)}: ${color.toString()};`);
+ }
+ }
+ ruleCollector.addRule(`.monaco-editor { ${colorVariables.join('\n')} }`);
+
const colorMap = this._colorMapOverride || this._theme.tokenTheme.getColorMap();
ruleCollector.addRule(generateTokensCSSForColorMap(colorMap));
diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts
index efc635346ac..b21d9f3e204 100644
--- a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts
+++ b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts
@@ -7,8 +7,8 @@ import assert = require('assert');
import { splitLines } from 'vs/base/common/strings';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
-import { BeforeEditPositionMapper, TextEditInfo } from 'vs/editor/common/model/bracketPairs/impl/beforeEditPositionMapper';
-import { Length, lengthOfString, lengthToObj, lengthToPosition, toLength } from 'vs/editor/common/model/bracketPairs/impl/length';
+import { BeforeEditPositionMapper, TextEditInfo } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/beforeEditPositionMapper';
+import { Length, lengthOfString, lengthToObj, lengthToPosition, toLength } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length';
suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => {
test('Single-Line 1', () => {
diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts
index 2f2ed1c5825..5d47baa9122 100644
--- a/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts
+++ b/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts
@@ -5,9 +5,9 @@
import assert = require('assert');
import { DisposableStore } from 'vs/base/common/lifecycle';
-import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairs/impl/brackets';
-import { SmallImmutableSet, DenseKeyProvider } from 'vs/editor/common/model/bracketPairs/impl/smallImmutableSet';
-import { Token, TokenKind } from 'vs/editor/common/model/bracketPairs/impl/tokenizer';
+import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/brackets';
+import { SmallImmutableSet, DenseKeyProvider } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet';
+import { Token, TokenKind } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts
index b75bb1fb05e..ca39ae3f3e3 100644
--- a/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts
+++ b/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
-import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairs/impl/ast';
-import { toLength } from 'vs/editor/common/model/bracketPairs/impl/length';
-import { concat23Trees } from 'vs/editor/common/model/bracketPairs/impl/concat23Trees';
+import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/ast';
+import { toLength } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length';
+import { concat23Trees } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/concat23Trees';
suite('Bracket Pair Colorizer - mergeItems', () => {
test('Clone', () => {
diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts
index 487564aba52..75a55bfaf47 100644
--- a/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts
+++ b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
-import { Length, lengthAdd, lengthDiffNonNegative, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairs/impl/length';
+import { Length, lengthAdd, lengthDiffNonNegative, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length';
suite('Bracket Pair Colorizer - Length', () => {
function toStr(length: Length): string {
diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts
index 7d91fe13013..f608e7da28b 100644
--- a/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts
+++ b/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
-import { DenseKeyProvider, SmallImmutableSet } from 'vs/editor/common/model/bracketPairs/impl/smallImmutableSet';
+import { DenseKeyProvider, SmallImmutableSet } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet';
suite('Bracket Pair Colorizer - ImmutableSet', () => {
test('Basic', () => {
diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts
index a87b325d821..d15f4ed7bf5 100644
--- a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts
+++ b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts
@@ -5,10 +5,10 @@
import assert = require('assert');
import { TokenizationResult2 } from 'vs/editor/common/core/token';
-import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairs/impl/brackets';
-import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/model/bracketPairs/impl/length';
-import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairs/impl/smallImmutableSet';
-import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairs/impl/tokenizer';
+import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/brackets';
+import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length';
+import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet';
+import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IState, ITokenizationSupport, LanguageId, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts
index a6d453b500e..6c903ced8e3 100644
--- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts
+++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts
@@ -8,7 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
-import { IFoundBracket } from 'vs/editor/common/model';
+import { IFoundBracket } from 'vs/editor/common/model/bracketPairs/bracketPairs';
import { TextModel } from 'vs/editor/common/model/textModel';
import { ITokenizationSupport, MetadataConsts, TokenizationRegistry, StandardTokenType } from 'vs/editor/common/modes';
import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration';
@@ -99,7 +99,7 @@ suite('TextModelWithTokens', () => {
}
}
- let actual = model.findPrevBracket({
+ let actual = model.bracketPairs.findPrevBracket({
lineNumber: lineNumber,
column: column
});
@@ -125,7 +125,7 @@ suite('TextModelWithTokens', () => {
}
}
- let actual = model.findNextBracket({
+ let actual = model.bracketPairs.findNextBracket({
lineNumber: lineNumber,
column: column
});
@@ -150,12 +150,12 @@ suite('TextModelWithTokens', () => {
});
function assertIsNotBracket(model: TextModel, lineNumber: number, column: number) {
- const match = model.matchBracket(new Position(lineNumber, column));
+ const match = model.bracketPairs.matchBracket(new Position(lineNumber, column));
assert.strictEqual(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column);
}
function assertIsBracket(model: TextModel, testPosition: Position, expected: [Range, Range]): void {
- const actual = model.matchBracket(testPosition);
+ const actual = model.bracketPairs.matchBracket(testPosition);
assert.deepStrictEqual(actual, expected, 'matches brackets at ' + testPosition);
}
@@ -442,7 +442,7 @@ suite('TextModelWithTokens', () => {
model.forceTokenization(2);
model.forceTokenization(3);
- assert.deepStrictEqual(model.matchBracket(new Position(2, 14)), [new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]);
+ assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 14)), [new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]);
disposables.dispose();
});
@@ -520,8 +520,8 @@ suite('TextModelWithTokens', () => {
model.forceTokenization(2);
model.forceTokenization(3);
- assert.deepStrictEqual(model.matchBracket(new Position(2, 23)), null);
- assert.deepStrictEqual(model.matchBracket(new Position(2, 20)), null);
+ assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 23)), null);
+ assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 20)), null);
disposables.dispose();
});
@@ -630,7 +630,7 @@ suite('TextModelWithTokens regression tests', () => {
'End Module',
].join('\n'), undefined, languageId));
- const actual = model.matchBracket(new Position(4, 1));
+ const actual = model.bracketPairs.matchBracket(new Position(4, 1));
assert.deepStrictEqual(actual, [new Range(4, 1, 4, 7), new Range(9, 1, 9, 11)]);
disposables.dispose();
@@ -655,7 +655,7 @@ suite('TextModelWithTokens regression tests', () => {
'endsequence',
].join('\n'), undefined, languageId));
- const actual = model.matchBracket(new Position(3, 9));
+ const actual = model.bracketPairs.matchBracket(new Position(3, 9));
assert.deepStrictEqual(actual, [new Range(3, 6, 3, 17), new Range(2, 6, 2, 14)]);
disposables.dispose();
diff --git a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts
index 0ac5ed2672d..f8dfc3eceec 100644
--- a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts
+++ b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts
@@ -58,7 +58,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => {
assert.strictEqual(indexOfResult.remainder, 3);
// [1, 2, 2, 1, 3]
- psc.changeValue(1, 2);
+ psc.setValue(1, 2);
assert.strictEqual(psc.getTotalSum(), 9);
assert.strictEqual(psc.getPrefixSum(0), 1);
assert.strictEqual(psc.getPrefixSum(1), 3);
@@ -67,7 +67,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => {
assert.strictEqual(psc.getPrefixSum(4), 9);
// [1, 0, 2, 1, 3]
- psc.changeValue(1, 0);
+ psc.setValue(1, 0);
assert.strictEqual(psc.getTotalSum(), 7);
assert.strictEqual(psc.getPrefixSum(0), 1);
assert.strictEqual(psc.getPrefixSum(1), 1);
@@ -100,7 +100,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => {
assert.strictEqual(indexOfResult.remainder, 3);
// [1, 0, 0, 1, 3]
- psc.changeValue(2, 0);
+ psc.setValue(2, 0);
assert.strictEqual(psc.getTotalSum(), 5);
assert.strictEqual(psc.getPrefixSum(0), 1);
assert.strictEqual(psc.getPrefixSum(1), 1);
@@ -127,7 +127,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => {
assert.strictEqual(indexOfResult.remainder, 3);
// [1, 0, 0, 0, 3]
- psc.changeValue(3, 0);
+ psc.setValue(3, 0);
assert.strictEqual(psc.getTotalSum(), 4);
assert.strictEqual(psc.getPrefixSum(0), 1);
assert.strictEqual(psc.getPrefixSum(1), 1);
@@ -151,9 +151,9 @@ suite('Editor ViewModel - PrefixSumComputer', () => {
assert.strictEqual(indexOfResult.remainder, 3);
// [1, 1, 0, 1, 1]
- psc.changeValue(1, 1);
- psc.changeValue(3, 1);
- psc.changeValue(4, 1);
+ psc.setValue(1, 1);
+ psc.setValue(3, 1);
+ psc.setValue(4, 1);
assert.strictEqual(psc.getTotalSum(), 4);
assert.strictEqual(psc.getPrefixSum(0), 1);
assert.strictEqual(psc.getPrefixSum(1), 2);
diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts
index a65305637ab..ab25a2773aa 100644
--- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts
+++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts
@@ -14,7 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import * as modes from 'vs/editor/common/modes';
import { NULL_STATE } from 'vs/editor/common/modes/nullMode';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
-import { ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
+import { ISimpleModel, ModelLineProjection, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { LineBreakData, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
@@ -946,8 +946,8 @@ function pos(lineNumber: number, column: number): Position {
return new Position(lineNumber, column);
}
-function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): SplitLine {
- return new SplitLine(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible);
+function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): ModelLineProjection {
+ return new ModelLineProjection(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible);
}
function createLineBreakData(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakData {
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
index 42a1e515946..a4b0713389c 100644
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -802,6 +802,10 @@ declare namespace monaco {
* Get the position at `positionLineNumber` and `positionColumn`.
*/
getPosition(): Position;
+ /**
+ * Get the position at the start of the selection.
+ */
+ getSelectionStart(): Position;
/**
* Create a new selection with a different `selectionStartLineNumber` and `selectionStartColumn`.
*/
@@ -810,6 +814,10 @@ declare namespace monaco {
* Create a `Selection` from one or two positions
*/
static fromPositions(start: IPosition, end?: IPosition): Selection;
+ /**
+ * Creates a `Selection` from a range, given a direction.
+ */
+ static fromRange(range: Range, direction: SelectionDirection): Selection;
/**
* Create a `Selection` from an `ISelection`.
*/
@@ -5817,6 +5825,11 @@ declare namespace monaco.languages {
InsertAsSnippet = 4
}
+ export interface CompletionItemRanges {
+ insert: IRange;
+ replace: IRange;
+ }
+
/**
* A completion item represents a text snippet that is
* proposed to complete text that is being typed.
@@ -5885,10 +5898,7 @@ declare namespace monaco.languages {
* *Note:* The range must be a {@link Range.isSingleLine single line} and it must
* {@link Range.contains contain} the position at which completion has been {@link CompletionItemProvider.provideCompletionItems requested}.
*/
- range: IRange | {
- insert: IRange;
- replace: IRange;
- };
+ range: IRange | CompletionItemRanges;
/**
* An optional set of characters that when pressed while this completion is active will accept it first and
* then type that character. *Note* that all commit characters should have `length=1` and that superfluous
diff --git a/src/vs/platform/assignment/common/assignment.ts b/src/vs/platform/assignment/common/assignment.ts
new file mode 100644
index 00000000000..513296737f0
--- /dev/null
+++ b/src/vs/platform/assignment/common/assignment.ts
@@ -0,0 +1,116 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as platform from 'vs/base/common/platform';
+import { IExperimentationFilterProvider } from 'tas-client-umd';
+
+export const ASSIGNMENT_STORAGE_KEY = 'VSCode.ABExp.FeatureData';
+export const ASSIGNMENT_REFETCH_INTERVAL = 0; // no polling
+
+export interface IAssignmentService {
+ readonly _serviceBrand: undefined;
+ getTreatment(name: string): Promise;
+}
+
+export enum TargetPopulation {
+ Team = 'team',
+ Internal = 'internal',
+ Insiders = 'insider',
+ Public = 'public',
+}
+
+/*
+Based upon the official VSCode currently existing filters in the
+ExP backend for the VSCode cluster.
+https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster
+"X-MSEdge-Market": "detection.market",
+"X-FD-Corpnet": "detection.corpnet",
+"X-VSCodeāAppVersion": "appversion",
+"X-VSCode-Build": "build",
+"X-MSEdge-ClientId": "clientid",
+"X-VSCode-ExtensionName": "extensionname",
+"X-VSCode-TargetPopulation": "targetpopulation",
+"X-VSCode-Language": "language"
+*/
+export enum Filters {
+ /**
+ * The market in which the extension is distributed.
+ */
+ Market = 'X-MSEdge-Market',
+
+ /**
+ * The corporation network.
+ */
+ CorpNet = 'X-FD-Corpnet',
+
+ /**
+ * Version of the application which uses experimentation service.
+ */
+ ApplicationVersion = 'X-VSCode-AppVersion',
+
+ /**
+ * Insiders vs Stable.
+ */
+ Build = 'X-VSCode-Build',
+
+ /**
+ * Client Id which is used as primary unit for the experimentation.
+ */
+ ClientId = 'X-MSEdge-ClientId',
+
+ /**
+ * Extension header.
+ */
+ ExtensionName = 'X-VSCode-ExtensionName',
+
+ /**
+ * The language in use by VS Code
+ */
+ Language = 'X-VSCode-Language',
+
+ /**
+ * The target population.
+ * This is used to separate internal, early preview, GA, etc.
+ */
+ TargetPopulation = 'X-VSCode-TargetPopulation',
+}
+
+export class AssignmentFilterProvider implements IExperimentationFilterProvider {
+ constructor(
+ private version: string,
+ private appName: string,
+ private machineId: string,
+ private targetPopulation: TargetPopulation
+ ) { }
+
+ getFilterValue(filter: string): string | null {
+ switch (filter) {
+ case Filters.ApplicationVersion:
+ return this.version; // productService.version
+ case Filters.Build:
+ return this.appName; // productService.nameLong
+ case Filters.ClientId:
+ return this.machineId;
+ case Filters.Language:
+ return platform.language;
+ case Filters.ExtensionName:
+ return 'vscode-core'; // always return vscode-core for exp service
+ case Filters.TargetPopulation:
+ return this.targetPopulation;
+ default:
+ return '';
+ }
+ }
+
+ getFilters(): Map {
+ let filters: Map = new Map();
+ let filterValues = Object.values(Filters);
+ for (let value of filterValues) {
+ filters.set(value, this.getFilterValue(value));
+ }
+
+ return filters;
+ }
+}
diff --git a/src/vs/platform/assignment/common/assignmentService.ts b/src/vs/platform/assignment/common/assignmentService.ts
new file mode 100644
index 00000000000..ad2997b1fec
--- /dev/null
+++ b/src/vs/platform/assignment/common/assignmentService.ts
@@ -0,0 +1,123 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import type { IExperimentationTelemetry, ExperimentationService as TASClient, IKeyValueStorage } from 'tas-client-umd';
+import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { IProductService } from 'vs/platform/product/common/productService';
+import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils';
+import { AssignmentFilterProvider, ASSIGNMENT_REFETCH_INTERVAL, ASSIGNMENT_STORAGE_KEY, IAssignmentService, TargetPopulation } from 'vs/platform/assignment/common/assignment';
+
+class NullAssignmentServiceTelemetry implements IExperimentationTelemetry {
+ constructor() { }
+
+ setSharedProperty(name: string, value: string): void {
+ // noop due to lack of telemetry service
+ }
+
+ postEvent(eventName: string, props: Map): void {
+ // noop due to lack of telemetry service
+ }
+}
+
+export abstract class BaseAssignmentService implements IAssignmentService {
+ _serviceBrand: undefined;
+ protected tasClient: Promise | undefined;
+ private networkInitialized = false;
+ private overrideInitDelay: Promise;
+
+ protected get experimentsEnabled(): boolean {
+ return this.configurationService.getValue('workbench.enableExperiments') === true;
+ }
+
+ constructor(
+ private readonly getMachineId: () => Promise,
+ private readonly configurationService: IConfigurationService,
+ protected readonly productService: IProductService,
+ protected telemetry: IExperimentationTelemetry,
+ private keyValueStorage?: IKeyValueStorage
+ ) {
+
+ if (productService.tasConfig && this.experimentsEnabled && getTelemetryLevel(this.configurationService) === TelemetryLevel.USAGE) {
+ this.tasClient = this.setupTASClient();
+ }
+
+ // For development purposes, configure the delay until tas local tas treatment ovverrides are available
+ const overrideDelaySetting = this.configurationService.getValue('experiments.overrideDelay');
+ const overrideDelay = typeof overrideDelaySetting === 'number' ? overrideDelaySetting : 0;
+ this.overrideInitDelay = new Promise(resolve => setTimeout(resolve, overrideDelay));
+ }
+
+ async getTreatment(name: string): Promise {
+ // For development purposes, allow overriding tas assignments to test variants locally.
+ await this.overrideInitDelay;
+ const override = this.configurationService.getValue('experiments.override.' + name);
+ if (override !== undefined) {
+ return override;
+ }
+
+ if (!this.tasClient) {
+ return undefined;
+ }
+
+ if (!this.experimentsEnabled) {
+ return undefined;
+ }
+
+ let result: T | undefined;
+ const client = await this.tasClient;
+
+ // The TAS client is initialized but we need to check if the initial fetch has completed yet
+ // If it is complete, return a cached value for the treatment
+ // If not, use the async call with `checkCache: true`. This will allow the module to return a cached value if it is present.
+ // Otherwise it will await the initial fetch to return the most up to date value.
+ if (this.networkInitialized) {
+ result = client.getTreatmentVariable('vscode', name);
+ } else {
+ result = await client.getTreatmentVariableAsync('vscode', name, true);
+ }
+
+ result = client.getTreatmentVariable('vscode', name);
+ return result;
+ }
+
+ private async setupTASClient(): Promise {
+ const targetPopulation = this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders;
+ const machineId = await this.getMachineId();
+ const filterProvider = new AssignmentFilterProvider(
+ this.productService.version,
+ this.productService.nameLong,
+ machineId,
+ targetPopulation
+ );
+
+ const tasConfig = this.productService.tasConfig!;
+ const tasClient = new (await import('tas-client-umd')).ExperimentationService({
+ filterProviders: [filterProvider],
+ telemetry: this.telemetry,
+ storageKey: ASSIGNMENT_STORAGE_KEY,
+ keyValueStorage: this.keyValueStorage,
+ featuresTelemetryPropertyName: tasConfig.featuresTelemetryPropertyName,
+ assignmentContextTelemetryPropertyName: tasConfig.assignmentContextTelemetryPropertyName,
+ telemetryEventName: tasConfig.telemetryEventName,
+ endpoint: tasConfig.endpoint,
+ refetchInterval: ASSIGNMENT_REFETCH_INTERVAL,
+ });
+
+ await tasClient.initializePromise;
+ tasClient.initialFetch.then(() => this.networkInitialized = true);
+
+ return tasClient;
+ }
+}
+
+export class AssignmentService extends BaseAssignmentService {
+ constructor(
+ machineId: string,
+ configurationService: IConfigurationService,
+ productService: IProductService) {
+ super(() => Promise.resolve(machineId), configurationService, productService, new NullAssignmentServiceTelemetry());
+ }
+}
diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts
index 116b0d45f96..9eac534a914 100644
--- a/src/vs/platform/configuration/test/common/testConfigurationService.ts
+++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts
@@ -6,13 +6,14 @@
import { Emitter } from 'vs/base/common/event';
import { TernarySearchTree } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
-import { getConfigurationKeys, getConfigurationValue, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
+import { getConfigurationKeys, getConfigurationValue, IConfigurationChangeEvent, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
export class TestConfigurationService implements IConfigurationService {
public _serviceBrand: undefined;
private configuration: any;
- readonly onDidChangeConfiguration = new Emitter().event;
+ readonly onDidChangeConfigurationEmitter = new Emitter();
+ readonly onDidChangeConfiguration = this.onDidChangeConfigurationEmitter.event;
constructor(configuration?: any) {
this.configuration = configuration || Object.create(null);
diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts
index 661f4eafdff..b7eca730c6e 100644
--- a/src/vs/platform/contextkey/common/contextkey.ts
+++ b/src/vs/platform/contextkey/common/contextkey.ts
@@ -81,50 +81,45 @@ export abstract class ContextKeyExpr {
public static false(): ContextKeyExpression {
return ContextKeyFalseExpr.INSTANCE;
}
-
public static true(): ContextKeyExpression {
return ContextKeyTrueExpr.INSTANCE;
}
-
public static has(key: string): ContextKeyExpression {
return ContextKeyDefinedExpr.create(key);
}
-
public static equals(key: string, value: any): ContextKeyExpression {
return ContextKeyEqualsExpr.create(key, value);
}
-
public static notEquals(key: string, value: any): ContextKeyExpression {
return ContextKeyNotEqualsExpr.create(key, value);
}
-
public static regex(key: string, value: RegExp): ContextKeyExpression {
return ContextKeyRegexExpr.create(key, value);
}
-
public static in(key: string, value: string): ContextKeyExpression {
return ContextKeyInExpr.create(key, value);
}
-
public static not(key: string): ContextKeyExpression {
return ContextKeyNotExpr.create(key);
}
-
public static and(...expr: Array): ContextKeyExpression | undefined {
return ContextKeyAndExpr.create(expr, null);
}
-
public static or(...expr: Array): ContextKeyExpression | undefined {
return ContextKeyOrExpr.create(expr, null, true);
}
-
- public static greater(key: string, value: any): ContextKeyExpression {
+ public static greater(key: string, value: number): ContextKeyExpression {
return ContextKeyGreaterExpr.create(key, value);
}
-
- public static less(key: string, value: any): ContextKeyExpression {
+ public static greaterEquals(key: string, value: number): ContextKeyExpression {
+ return ContextKeyGreaterEqualsExpr.create(key, value);
+ }
+ public static smaller(key: string, value: number): ContextKeyExpression {
return ContextKeySmallerExpr.create(key, value);
}
+ public static smallerEquals(key: string, value: number): ContextKeyExpression {
+ return ContextKeySmallerEqualsExpr.create(key, value);
+ }
public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpression | undefined {
if (!serialized) {
@@ -741,17 +736,30 @@ export class ContextKeyNotExpr implements IContextKeyExpression {
}
}
+function withFloatOrStr(value: any, callback: (value: number | string) => T): T | ContextKeyFalseExpr {
+ if (typeof value === 'string') {
+ const n = parseFloat(value);
+ if (!isNaN(n)) {
+ value = n;
+ }
+ }
+ if (typeof value === 'string' || typeof value === 'number') {
+ return callback(value);
+ }
+ return ContextKeyFalseExpr.INSTANCE;
+}
+
export class ContextKeyGreaterExpr implements IContextKeyExpression {
- public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
- return new ContextKeyGreaterExpr(key, value, negated);
+ public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
+ return withFloatOrStr(_value, (value) => new ContextKeyGreaterExpr(key, value, negated));
}
public readonly type = ContextKeyExprType.Greater;
private constructor(
private readonly key: string,
- private readonly value: any,
+ private readonly value: number | string,
private negated: ContextKeyExpression | null
) { }
@@ -774,7 +782,10 @@ export class ContextKeyGreaterExpr implements IContextKeyExpression {
}
public evaluate(context: IContext): boolean {
- return (parseFloat(context.getValue(this.key)) > parseFloat(this.value));
+ if (typeof this.value === 'string') {
+ return false;
+ }
+ return (parseFloat(context.getValue(this.key)) > this.value);
}
public serialize(): string {
@@ -799,15 +810,15 @@ export class ContextKeyGreaterExpr implements IContextKeyExpression {
export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression {
- public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
- return new ContextKeyGreaterEqualsExpr(key, value, negated);
+ public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
+ return withFloatOrStr(_value, (value) => new ContextKeyGreaterEqualsExpr(key, value, negated));
}
public readonly type = ContextKeyExprType.GreaterEquals;
private constructor(
private readonly key: string,
- private readonly value: any,
+ private readonly value: number | string,
private negated: ContextKeyExpression | null
) { }
@@ -830,7 +841,10 @@ export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression {
}
public evaluate(context: IContext): boolean {
- return (parseFloat(context.getValue(this.key)) >= parseFloat(this.value));
+ if (typeof this.value === 'string') {
+ return false;
+ }
+ return (parseFloat(context.getValue(this.key)) >= this.value);
}
public serialize(): string {
@@ -855,15 +869,15 @@ export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression {
export class ContextKeySmallerExpr implements IContextKeyExpression {
- public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
- return new ContextKeySmallerExpr(key, value, negated);
+ public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
+ return withFloatOrStr(_value, (value) => new ContextKeySmallerExpr(key, value, negated));
}
public readonly type = ContextKeyExprType.Smaller;
private constructor(
private readonly key: string,
- private readonly value: any,
+ private readonly value: number | string,
private negated: ContextKeyExpression | null
) {
}
@@ -887,7 +901,10 @@ export class ContextKeySmallerExpr implements IContextKeyExpression {
}
public evaluate(context: IContext): boolean {
- return (parseFloat(context.getValue(this.key)) < parseFloat(this.value));
+ if (typeof this.value === 'string') {
+ return false;
+ }
+ return (parseFloat(context.getValue(this.key)) < this.value);
}
public serialize(): string {
@@ -912,15 +929,15 @@ export class ContextKeySmallerExpr implements IContextKeyExpression {
export class ContextKeySmallerEqualsExpr implements IContextKeyExpression {
- public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
- return new ContextKeySmallerEqualsExpr(key, value, negated);
+ public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
+ return withFloatOrStr(_value, (value) => new ContextKeySmallerEqualsExpr(key, value, negated));
}
public readonly type = ContextKeyExprType.SmallerEquals;
private constructor(
private readonly key: string,
- private readonly value: any,
+ private readonly value: number | string,
private negated: ContextKeyExpression | null
) {
}
@@ -944,7 +961,10 @@ export class ContextKeySmallerEqualsExpr implements IContextKeyExpression {
}
public evaluate(context: IContext): boolean {
- return (parseFloat(context.getValue(this.key)) <= parseFloat(this.value));
+ if (typeof this.value === 'string') {
+ return false;
+ }
+ return (parseFloat(context.getValue(this.key)) <= this.value);
}
public serialize(): string {
diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts
index 165ff2ead59..94f807c0e38 100644
--- a/src/vs/platform/contextkey/test/common/contextkey.test.ts
+++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
-import { ContextKeyExpr, implies } from 'vs/platform/contextkey/common/contextkey';
+import { ContextKeyExpr, ContextKeyExpression, implies } from 'vs/platform/contextkey/common/contextkey';
function createContext(ctx: any) {
return {
@@ -45,6 +45,19 @@ suite('ContextKeyExpr', () => {
assert(a.equals(b), 'expressions should be equal');
});
+ test('issue #134942: Equals in comparator expressions', () => {
+ function testEquals(expr: ContextKeyExpression | undefined, str: string): void {
+ const deserialized = ContextKeyExpr.deserialize(str);
+ assert.ok(expr);
+ assert.ok(deserialized);
+ assert.strictEqual(expr.equals(deserialized), true, str);
+ }
+ testEquals(ContextKeyExpr.greater('value', 0), 'value > 0');
+ testEquals(ContextKeyExpr.greaterEquals('value', 0), 'value >= 0');
+ testEquals(ContextKeyExpr.smaller('value', 0), 'value < 0');
+ testEquals(ContextKeyExpr.smallerEquals('value', 0), 'value <= 0');
+ });
+
test('normalize', () => {
let key1IsTrue = ContextKeyExpr.equals('key1', true);
let key1IsNotFalse = ContextKeyExpr.notEquals('key1', false);
diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts
index 81b53140b5e..72971fa7795 100644
--- a/src/vs/platform/diagnostics/node/diagnosticsService.ts
+++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts
@@ -56,7 +56,9 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P
{ tag: 'sln', filePattern: /^.+\.sln$/i },
{ tag: 'csproj', filePattern: /^.+\.csproj$/i },
{ tag: 'cmake', filePattern: /^.+\.cmake$/i },
- { tag: 'github-actions', filePattern: /^.+\.yml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i }
+ { tag: 'github-actions', filePattern: /^.+\.yml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i },
+ { tag: 'devcontainer.json', filePattern: /^devcontainer\.json$/i },
+ { tag: 'dockerfile', filePattern: /^(dockerfile|docker\-compose\.ya?ml)$/i }
];
const fileTypes = new Map();
diff --git a/src/vs/platform/environment/node/shellEnv.ts b/src/vs/platform/environment/node/shellEnv.ts
index 5d3fc1b38bd..de217e36abe 100644
--- a/src/vs/platform/environment/node/shellEnv.ts
+++ b/src/vs/platform/environment/node/shellEnv.ts
@@ -134,7 +134,12 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio
shellArgs = ['-Login', '-Command'];
} else {
command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`;
- shellArgs = ['-ilc'];
+
+ if (name === 'tcsh') {
+ shellArgs = ['-ic'];
+ } else {
+ shellArgs = ['-ilc'];
+ }
}
logService.trace('getUnixShellEnvironment#spawn', JSON.stringify(shellArgs), command);
diff --git a/src/vs/platform/environment/test/node/nativeModules.test.ts b/src/vs/platform/environment/test/node/nativeModules.test.ts
index 3cb1ee1a509..130d0561679 100644
--- a/src/vs/platform/environment/test/node/nativeModules.test.ts
+++ b/src/vs/platform/environment/test/node/nativeModules.test.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
-import { isMacintosh, isWindows } from 'vs/base/common/platform';
+import { isWindows } from 'vs/base/common/platform';
function testErrorMessage(module: string): string {
return `Unable to load "${module}" dependency. It was probably not compiled for the right operating system architecture or had missing build tools.`;
@@ -53,17 +53,6 @@ suite('Native Modules (all platforms)', () => {
});
});
-(!isMacintosh ? suite.skip : suite)('Native Modules (macOS)', () => {
-
- test('chokidar (fsevents)', async () => {
- const chokidar = await import('chokidar');
- const watcher = chokidar.watch(__dirname);
- assert.ok(watcher.options.useFsEvents, testErrorMessage('chokidar (fsevents)'));
-
- return watcher.close();
- });
-});
-
(!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => {
test('windows-mutex', async () => {
diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
index 9bda0d7d610..55aff88346b 100644
--- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
+++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
@@ -12,11 +12,8 @@ import { joinPath } from 'vs/base/common/resources';
import { isString } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
-import { createFileSystemProviderError, FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
-
-const INDEXEDDB_VSCODE_DB = 'vscode-web-db';
-export const INDEXEDDB_USERDATA_OBJECT_STORE = 'vscode-userdata-store';
-export const INDEXEDDB_LOGS_OBJECT_STORE = 'vscode-logs-store';
+import { createFileSystemProviderError, FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IStat, IWatchOptions } from 'vs/platform/files/common/files';
+import { IndexedDB } from 'vs/base/browser/indexedDB';
// Standard FS Errors (expected to be thrown in production when invalid FS operations are requested)
const ERR_FILE_NOT_FOUND = createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound);
@@ -27,98 +24,6 @@ const ERR_DIR_NOT_EMPTY = createFileSystemProviderError(localize('dirIsNotEmpty'
// Arbitrary Internal Errors (should never be thrown in production)
const ERR_UNKNOWN_INTERNAL = (message: string) => createFileSystemProviderError(localize('internal', "Internal error occurred in IndexedDB File System Provider. ({0})", message), FileSystemProviderErrorCode.Unknown);
-class MissingStoresError extends Error {
- constructor(readonly db: IDBDatabase) {
- super('Missing stores');
- }
-}
-
-export class IndexedDB {
-
- private indexedDBPromise: Promise;
-
- constructor() {
- this.indexedDBPromise = this.openIndexedDB(INDEXEDDB_VSCODE_DB, 2, [INDEXEDDB_USERDATA_OBJECT_STORE, INDEXEDDB_LOGS_OBJECT_STORE]);
- }
-
- async createFileSystemProvider(scheme: string, store: string, watchCrossWindowChanges: boolean): Promise {
- let fsp: IIndexedDBFileSystemProvider | null = null;
- const indexedDB = await this.indexedDBPromise;
- if (indexedDB) {
- if (indexedDB.objectStoreNames.contains(store)) {
- fsp = new IndexedDBFileSystemProvider(scheme, indexedDB, store, watchCrossWindowChanges);
- } else {
- console.error(`Error while creating indexedDB filesystem provider. Could not find ${store} object store`);
- }
- }
- return fsp;
- }
-
- private async openIndexedDB(name: string, version: number, stores: string[]): Promise {
- try {
- return await this.createIndexedDB(name, version, stores);
- } catch (err) {
- if (err instanceof MissingStoresError) {
- console.info(`Attempting to recreate the indexedDB once.`, name);
-
- try {
- // Try to delete the db
- await this.deleteIndexedDB(err.db);
- } catch (error) {
- console.error(`Error while deleting the indexedDB`, getErrorMessage(error));
- throw error;
- }
-
- return await this.createIndexedDB(name, version, stores);
- }
-
- throw err;
- }
- }
-
- private createIndexedDB(name: string, version: number, stores: string[]): Promise {
- return new Promise((c, e) => {
- const request = window.indexedDB.open(name, version);
- request.onerror = () => e(request.error);
- request.onsuccess = () => {
- const db = request.result;
- for (const store of stores) {
- if (!db.objectStoreNames.contains(store)) {
- console.error(`Error while opening indexedDB. Could not find ${store} object store`);
- e(new MissingStoresError(db));
- return;
- }
- }
- c(db);
- };
- request.onupgradeneeded = () => {
- const db = request.result;
- for (const store of stores) {
- if (!db.objectStoreNames.contains(store)) {
- db.createObjectStore(store);
- }
- }
- };
- });
- }
-
- private deleteIndexedDB(indexedDB: IDBDatabase): Promise {
- return new Promise((c, e) => {
- // Close any opened connections
- indexedDB.close();
-
- // Delete the db
- const deleteRequest = window.indexedDB.deleteDatabase(indexedDB.name);
- deleteRequest.onerror = (err) => e(deleteRequest.error);
- deleteRequest.onsuccess = () => c();
- });
- }
-}
-
-export interface IIndexedDBFileSystemProvider extends Disposable, IFileSystemProviderWithFileReadWriteCapability {
- reset(): Promise;
-}
-
type DirEntry = [string, FileType];
type IndexedDBFileSystemEntry =
@@ -319,7 +224,7 @@ class IndexedDBChangesBroadcastChannel extends Disposable {
}
-class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSystemProvider {
+export class IndexedDBFileSystemProvider extends Disposable {
readonly capabilities: FileSystemProviderCapabilities =
FileSystemProviderCapabilities.FileReadWrite
@@ -335,7 +240,7 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy
private cachedFiletree: Promise | undefined;
private writeManyThrottler: Throttler;
- constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string, watchCrossWindowChanges: boolean) {
+ constructor(scheme: string, private indexedDB: IndexedDB, private readonly store: string, watchCrossWindowChanges: boolean) {
super();
this.writeManyThrottler = new Throttler();
@@ -400,29 +305,19 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy
}
async readFile(resource: URI): Promise {
- const buffer = await new Promise((c, e) => {
- const transaction = this.database.transaction([this.store]);
- transaction.oncomplete = () => {
- if (request.result instanceof Uint8Array) {
- c(request.result);
- } else if (typeof request.result === 'string') {
- c(VSBuffer.fromString(request.result).buffer);
- }
- else {
- if (request.result === undefined) {
- e(ERR_FILE_NOT_FOUND);
- } else {
- e(ERR_UNKNOWN_INTERNAL(`IndexedDB entry at "${resource.path}" in unexpected format`));
- }
- }
- };
- transaction.onerror = () => e(transaction.error);
+ const result = await this.indexedDB.runInTransaction(this.store, 'readonly', objectStore => objectStore.get(resource.path));
+ if (result === undefined) {
+ throw ERR_FILE_NOT_FOUND;
+ }
+ const buffer = result instanceof Uint8Array ? result : isString(result) ? VSBuffer.fromString(result).buffer : undefined;
+ if (buffer === undefined) {
+ throw ERR_UNKNOWN_INTERNAL(`IndexedDB entry at "${resource.path}" in unexpected format`);
+ }
- const objectStore = transaction.objectStore(this.store);
- const request = objectStore.get(resource.path);
- });
+ // update cache
+ const fileTree = await this.getFiletree();
+ fileTree.add(resource.path, { type: 'file', size: buffer.byteLength });
- (await this.getFiletree()).add(resource.path, { type: 'file', size: buffer.byteLength });
return buffer;
}
@@ -502,70 +397,37 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy
private getFiletree(): Promise {
if (!this.cachedFiletree) {
- this.cachedFiletree = new Promise((c, e) => {
- const transaction = this.database.transaction([this.store]);
- transaction.oncomplete = () => {
- const rootNode = new IndexedDBFileSystemNode({
- children: new Map(),
- path: '',
- type: FileType.Directory
- });
- const keys = request.result.map(key => key.toString());
- keys.forEach(key => rootNode.add(key, { type: 'file' }));
- c(rootNode);
- };
- transaction.onerror = () => e(transaction.error);
-
- const objectStore = transaction.objectStore(this.store);
- const request = objectStore.getAllKeys();
- });
+ this.cachedFiletree = (async () => {
+ const rootNode = new IndexedDBFileSystemNode({
+ children: new Map(),
+ path: '',
+ type: FileType.Directory
+ });
+ const result = await this.indexedDB.runInTransaction(this.store, 'readonly', objectStore => objectStore.getAllKeys());
+ const keys = result.map(key => key.toString());
+ keys.forEach(key => rootNode.add(key, { type: 'file' }));
+ return rootNode;
+ })();
}
return this.cachedFiletree;
}
private fileWriteBatch: { resource: URI, content: Uint8Array }[] = [];
private async writeMany() {
- return new Promise((c, e) => {
- const fileBatch = this.fileWriteBatch;
- this.fileWriteBatch = [];
- if (fileBatch.length === 0) {
- return c();
- }
-
- const transaction = this.database.transaction([this.store], 'readwrite');
- transaction.oncomplete = () => c();
- transaction.onerror = () => e(transaction.error);
- const objectStore = transaction.objectStore(this.store);
- for (const entry of fileBatch) {
- objectStore.put(entry.content, entry.resource.path);
- }
- });
+ if (this.fileWriteBatch.length) {
+ const fileBatch = this.fileWriteBatch.splice(0, this.fileWriteBatch.length);
+ await this.indexedDB.runInTransaction(this.store, 'readwrite', objectStore => fileBatch.map(entry => objectStore.put(entry.content, entry.resource.path)));
+ }
}
- private deleteKeys(keys: string[]): Promise {
- return new Promise((c, e) => {
- if (keys.length === 0) {
- return c();
- }
-
- const transaction = this.database.transaction([this.store], 'readwrite');
- transaction.oncomplete = () => c();
- transaction.onerror = () => e(transaction.error);
- const objectStore = transaction.objectStore(this.store);
- for (const key of keys) {
- objectStore.delete(key);
- }
- });
+ private async deleteKeys(keys: string[]): Promise {
+ if (keys.length) {
+ await this.indexedDB.runInTransaction(this.store, 'readwrite', objectStore => keys.map(key => objectStore.delete(key)));
+ }
}
- reset(): Promise {
- return new Promise((c, e) => {
- const transaction = this.database.transaction([this.store], 'readwrite');
- transaction.oncomplete = () => c();
- transaction.onerror = () => e(transaction.error);
-
- const objectStore = transaction.objectStore(this.store);
- objectStore.clear();
- });
+ async reset(): Promise {
+ await this.indexedDB.runInTransaction(this.store, 'readwrite', objectStore => objectStore.clear());
}
+
}
diff --git a/src/vs/platform/files/common/ipcFileSystemProvider.ts b/src/vs/platform/files/common/ipcFileSystemProvider.ts
index b01ac4ff8f2..c85d2436745 100644
--- a/src/vs/platform/files/common/ipcFileSystemProvider.ts
+++ b/src/vs/platform/files/common/ipcFileSystemProvider.ts
@@ -7,7 +7,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { canceled } from 'vs/base/common/errors';
-import { Emitter } from 'vs/base/common/event';
+import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { newWriteableStream, ReadableStreamEventPayload, ReadableStreamEvents } from 'vs/base/common/stream';
import { URI, UriComponents } from 'vs/base/common/uri';
@@ -19,13 +19,13 @@ import { createFileSystemProviderError, FileChangeType, FileDeleteOptions, FileO
* An implementation of a file system provider that is backed by a `IChannel`
* and thus implemented via IPC on a different process.
*/
-export abstract class IPCFileSystemProvider extends Disposable implements
+export class IPCFileSystemProvider extends Disposable implements
IFileSystemProviderWithFileReadWriteCapability,
IFileSystemProviderWithOpenReadWriteCloseCapability,
IFileSystemProviderWithFileReadStreamCapability,
IFileSystemProviderWithFileFolderCopyCapability {
- constructor(private readonly channel: IChannel) {
+ constructor(private readonly channel: IChannel, private readonly extraCapabilities: { trash?: boolean, pathCaseSensitive?: boolean }) {
super();
this.registerFileChangeListeners();
@@ -33,24 +33,28 @@ export abstract class IPCFileSystemProvider extends Disposable implements
//#region File Capabilities
- private readonly _onDidChangeCapabilities = this._register(new Emitter());
- readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;
+ readonly onDidChangeCapabilities: Event = Event.None;
- private _capabilities = FileSystemProviderCapabilities.FileReadWrite
- | FileSystemProviderCapabilities.FileOpenReadWriteClose
- | FileSystemProviderCapabilities.FileReadStream
- | FileSystemProviderCapabilities.FileFolderCopy
- | FileSystemProviderCapabilities.FileWriteUnlock;
- get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }
+ private _capabilities: FileSystemProviderCapabilities | undefined;
+ get capabilities(): FileSystemProviderCapabilities {
+ if (!this._capabilities) {
+ this._capabilities =
+ FileSystemProviderCapabilities.FileReadWrite |
+ FileSystemProviderCapabilities.FileOpenReadWriteClose |
+ FileSystemProviderCapabilities.FileReadStream |
+ FileSystemProviderCapabilities.FileFolderCopy |
+ FileSystemProviderCapabilities.FileWriteUnlock;
- protected setCaseSensitive(isCaseSensitive: boolean) {
- if (isCaseSensitive) {
- this._capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
- } else {
- this._capabilities &= ~FileSystemProviderCapabilities.PathCaseSensitive;
+ if (this.extraCapabilities.pathCaseSensitive) {
+ this._capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
+ }
+
+ if (this.extraCapabilities.trash) {
+ this._capabilities |= FileSystemProviderCapabilities.Trash;
+ }
}
- this._onDidChangeCapabilities.fire();
+ return this._capabilities;
}
//#endregion
diff --git a/src/vs/platform/files/common/watcher.ts b/src/vs/platform/files/common/watcher.ts
index dd4e8298cee..4314712e66b 100644
--- a/src/vs/platform/files/common/watcher.ts
+++ b/src/vs/platform/files/common/watcher.ts
@@ -162,8 +162,8 @@ export interface IWatchRequest {
excludes: string[];
/**
- * @deprecated TODO@bpasero TODO@aeschli remove me once WSL1
- * support ends.
+ * @deprecated this only exists for WSL1 support and should never
+ * be used in any other case.
*/
pollingInterval?: number;
}
diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts
index 1c1a1284972..cb867f1a057 100644
--- a/src/vs/platform/files/node/diskFileSystemProvider.ts
+++ b/src/vs/platform/files/node/diskFileSystemProvider.ts
@@ -23,10 +23,8 @@ import { readFileIntoStream } from 'vs/platform/files/common/io';
import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService';
import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService';
import { FileWatcher as ParcelWatcherService } from 'vs/platform/files/node/watcher/parcel/watcherService';
-import { FileWatcher as UnixWatcherService } from 'vs/platform/files/node/watcher/unix/watcherService';
import { IDiskFileChange, ILogMessage, IWatchRequest, WatcherService } from 'vs/platform/files/common/watcher';
import { ILogService } from 'vs/platform/log/common/log';
-import product from 'vs/platform/product/common/product';
import { AbstractDiskFileSystemProvider } from 'vs/platform/files/common/diskFileSystemProvider';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -48,8 +46,8 @@ export interface IWatcherOptions {
* If `true`, will enable polling for all watchers, otherwise
* will enable it for paths included in the string array.
*
- * @deprecated TODO@bpasero TODO@aeschli remove me once WSL1
- * support ends.
+ * @deprecated this only exists for WSL1 support and should never
+ * be used in any other case.
*/
usePolling: boolean | string[];
@@ -57,8 +55,8 @@ export interface IWatcherOptions {
* If polling is enabled (via `usePolling`), defines the duration
* in which the watcher will poll for changes.
*
- * @deprecated TODO@bpasero TODO@aeschli remove me once WSL1
- * support ends.
+ * @deprecated this only exists for WSL1 support and should never
+ * be used in any other case.
*/
pollingInterval?: number;
}
@@ -570,25 +568,13 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
let enableLegacyWatcher = false;
if (this.options?.watcher?.usePolling) {
- enableLegacyWatcher = false; // can use Parcel watcher for when polling is required
+ enableLegacyWatcher = false; // must use Parcel watcher for when polling is required
} else {
- if (this.options?.legacyWatcher === 'on' || this.options?.legacyWatcher === 'off') {
- enableLegacyWatcher = this.options.legacyWatcher === 'on'; // setting always wins
- } else {
- if (product.quality === 'stable') {
- // in stable use legacy for single folder workspaces
- // TODO@bpasero remove me eventually
- enableLegacyWatcher = folders === 1;
- }
- }
+ enableLegacyWatcher = this.options?.legacyWatcher === 'on'; // setting always wins
}
if (enableLegacyWatcher) {
- if (isLinux) {
- watcherImpl = UnixWatcherService;
- } else {
- watcherImpl = NsfwWatcherService;
- }
+ watcherImpl = NsfwWatcherService;
} else {
watcherImpl = ParcelWatcherService;
}
diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts
deleted file mode 100644
index 495a823a0c6..00000000000
--- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts
+++ /dev/null
@@ -1,374 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as chokidar from 'chokidar';
-import * as fs from 'fs';
-import * as gracefulFs from 'graceful-fs';
-import { equals } from 'vs/base/common/arrays';
-import { ThrottledDelayer } from 'vs/base/common/async';
-import { Emitter } from 'vs/base/common/event';
-import { isEqualOrParent } from 'vs/base/common/extpath';
-import { match, parse, ParsedPattern } from 'vs/base/common/glob';
-import { Disposable } from 'vs/base/common/lifecycle';
-import { normalizeNFC } from 'vs/base/common/normalization';
-import { isLinux, isMacintosh } from 'vs/base/common/platform';
-import { realcaseSync } from 'vs/base/node/extpath';
-import { FileChangeType } from 'vs/platform/files/common/files';
-import { IWatcherOptions, IWatcherService } from 'vs/platform/files/node/watcher/unix/watcher';
-import { IDiskFileChange, ILogMessage, IWatchRequest, normalizeFileChanges } from 'vs/platform/files/common/watcher';
-
-gracefulFs.gracefulify(fs); // enable gracefulFs
-
-process.noAsar = true; // disable ASAR support in watcher process
-
-interface IWatcher {
- requests: ExtendedWatcherRequest[];
- stop(): Promise;
-}
-
-interface ExtendedWatcherRequest extends IWatchRequest {
- parsedPattern?: ParsedPattern;
-}
-
-export class ChokidarWatcherService extends Disposable implements IWatcherService {
-
- private static readonly FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms)
- private static readonly EVENT_SPAM_WARNING_THRESHOLD = 60 * 1000; // warn after certain time span of event spam
-
- private readonly _onDidChangeFile = this._register(new Emitter());
- readonly onDidChangeFile = this._onDidChangeFile.event;
-
- private readonly _onDidLogMessage = this._register(new Emitter());
- readonly onDidLogMessage = this._onDidLogMessage.event;
-
- private watchers = new Map();
-
- private _watcherCount = 0;
- get wacherCount() { return this._watcherCount; }
-
- private pollingInterval?: number;
- private usePolling?: boolean | string[];
- private verboseLogging: boolean | undefined;
-
- private spamCheckStartTime: number | undefined;
- private spamWarningLogged: boolean | undefined;
- private enospcErrorLogged: boolean | undefined;
-
- async init(options: IWatcherOptions): Promise {
- this.pollingInterval = options.pollingInterval;
- this.usePolling = options.usePolling;
- this.watchers.clear();
- this._watcherCount = 0;
- this.verboseLogging = options.verboseLogging;
- }
-
- async setVerboseLogging(enabled: boolean): Promise {
- this.verboseLogging = enabled;
- }
-
- async watch(requests: IWatchRequest[]): Promise {
- const watchers = new Map();
- const newRequests: string[] = [];
-
- const requestsByBasePath = normalizeRoots(requests);
-
- // evaluate new & remaining watchers
- for (const basePath in requestsByBasePath) {
- const watcher = this.watchers.get(basePath);
- if (watcher && isEqualRequests(watcher.requests, requestsByBasePath[basePath])) {
- watchers.set(basePath, watcher);
- this.watchers.delete(basePath);
- } else {
- newRequests.push(basePath);
- }
- }
-
- // stop all old watchers
- for (const [, watcher] of this.watchers) {
- await watcher.stop();
- }
-
- // start all new watchers
- for (const basePath of newRequests) {
- const requests = requestsByBasePath[basePath];
- watchers.set(basePath, this.doWatch(basePath, requests));
- }
-
- this.watchers = watchers;
- }
-
- private doWatch(basePath: string, requests: IWatchRequest[]): IWatcher {
- const pollingInterval = this.pollingInterval || 5000;
- let usePolling = this.usePolling; // boolean or a list of path patterns
- if (Array.isArray(usePolling)) {
- // switch to polling if one of the paths matches with a watched path
- usePolling = usePolling.some(pattern => requests.some(request => match(pattern, request.path)));
- }
-
- const watcherOpts: chokidar.WatchOptions = {
- ignoreInitial: true,
- ignorePermissionErrors: true,
- followSymlinks: true, // this is the default of chokidar and supports file events through symlinks
- interval: pollingInterval, // while not used in normal cases, if any error causes chokidar to fallback to polling, increase its intervals
- binaryInterval: pollingInterval,
- usePolling,
- disableGlobbing: true // fix https://github.com/microsoft/vscode/issues/4586
- };
-
- const excludes: string[] = [];
-
- const isSingleFolder = requests.length === 1;
- if (isSingleFolder) {
- excludes.push(...requests[0].excludes); // if there's only one request, use the built-in ignore-filterering
- }
-
- if ((isMacintosh || isLinux) && (basePath.length === 0 || basePath === '/')) {
- excludes.push('/dev/**');
- if (isLinux) {
- excludes.push('/proc/**', '/sys/**');
- }
- }
-
- excludes.push('**/*.asar'); // Ensure we never recurse into ASAR archives
-
- watcherOpts.ignored = excludes;
-
- // Chokidar fails when the basePath does not match case-identical to the path on disk
- // so we have to find the real casing of the path and do some path massaging to fix this
- // see https://github.com/paulmillr/chokidar/issues/418
- const realBasePath = isMacintosh ? (realcaseSync(basePath) || basePath) : basePath;
- const realBasePathLength = realBasePath.length;
- const realBasePathDiffers = (basePath !== realBasePath);
-
- if (realBasePathDiffers) {
- this.warn(`Watcher basePath does not match version on disk and was corrected (original: ${basePath}, real: ${realBasePath})`);
- }
-
- this.debug(`Start watching: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`);
-
- let chokidarWatcher: chokidar.FSWatcher | null = chokidar.watch(realBasePath, watcherOpts);
- this._watcherCount++;
-
- // Detect if for some reason the native watcher library fails to load
- if (isMacintosh && chokidarWatcher.options && !chokidarWatcher.options.useFsEvents) {
- this.warn('Watcher is not using native fsevents library and is falling back to unefficient polling.');
- }
-
- let undeliveredFileEvents: IDiskFileChange[] = [];
- let fileEventDelayer: ThrottledDelayer | null = new ThrottledDelayer(ChokidarWatcherService.FS_EVENT_DELAY);
-
- const watcher: IWatcher = {
- requests,
- stop: async () => {
- try {
- if (this.verboseLogging) {
- this.log(`Stop watching: ${basePath}]`);
- }
-
- if (chokidarWatcher) {
- await chokidarWatcher.close();
- this._watcherCount--;
- chokidarWatcher = null;
- }
-
- if (fileEventDelayer) {
- fileEventDelayer.cancel();
- fileEventDelayer = null;
- }
- } catch (error) {
- this.warn('Error while stopping watcher: ' + error.toString());
- }
- }
- };
-
- chokidarWatcher.on('all', (type: string, path: string) => {
- if (isMacintosh) {
- // Mac: uses NFD unicode form on disk, but we want NFC
- // See also https://github.com/nodejs/node/issues/2165
- path = normalizeNFC(path);
- }
-
- if (path.indexOf(realBasePath) < 0) {
- return; // we really only care about absolute paths here in our basepath context here
- }
-
- // Make sure to convert the path back to its original basePath form if the realpath is different
- if (realBasePathDiffers) {
- path = basePath + path.substr(realBasePathLength);
- }
-
- let eventType: FileChangeType;
- switch (type) {
- case 'change':
- eventType = FileChangeType.UPDATED;
- break;
- case 'add':
- case 'addDir':
- eventType = FileChangeType.ADDED;
- break;
- case 'unlink':
- case 'unlinkDir':
- eventType = FileChangeType.DELETED;
- break;
- default:
- return;
- }
-
- // if there's more than one request we need to do
- // extra filtering due to potentially overlapping roots
- if (!isSingleFolder) {
- if (isIgnored(path, watcher.requests)) {
- return;
- }
- }
-
- const event = { type: eventType, path };
-
- // Logging
- if (this.verboseLogging) {
- this.log(`${eventType === FileChangeType.ADDED ? '[ADDED]' : eventType === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${path}`);
- }
-
- // Check for spam
- const now = Date.now();
- if (undeliveredFileEvents.length === 0) {
- this.spamWarningLogged = false;
- this.spamCheckStartTime = now;
- } else if (!this.spamWarningLogged && typeof this.spamCheckStartTime === 'number' && this.spamCheckStartTime + ChokidarWatcherService.EVENT_SPAM_WARNING_THRESHOLD < now) {
- this.spamWarningLogged = true;
- this.warn(`Watcher is busy catching up with ${undeliveredFileEvents.length} file changes in 60 seconds. Latest changed path is "${event.path}"`);
- }
-
- // Add to buffer
- undeliveredFileEvents.push(event);
-
- if (fileEventDelayer) {
-
- // Delay and send buffer
- fileEventDelayer.trigger(async () => {
- const events = undeliveredFileEvents;
- undeliveredFileEvents = [];
-
- // Broadcast to clients normalized
- const normalizedEvents = normalizeFileChanges(events);
- this._onDidChangeFile.fire(normalizedEvents);
-
- // Logging
- if (this.verboseLogging) {
- for (const e of normalizedEvents) {
- this.log(` >> normalized ${e.type === FileChangeType.ADDED ? '[ADDED]' : e.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${e.path}`);
- }
- }
-
- return undefined;
- });
- }
- });
-
- chokidarWatcher.on('error', (error: NodeJS.ErrnoException) => {
- if (error) {
-
- // Specially handle ENOSPC errors that can happen when
- // the watcher consumes so many file descriptors that
- // we are running into a limit. We only want to warn
- // once in this case to avoid log spam.
- // See https://github.com/microsoft/vscode/issues/7950
- if (error.code === 'ENOSPC') {
- if (!this.enospcErrorLogged) {
- this.enospcErrorLogged = true;
- this.stop();
- this.error('Inotify limit reached (ENOSPC)');
- }
- } else {
- this.warn(error.toString());
- }
- }
- });
- return watcher;
- }
-
- async stop(): Promise {
- for (const [, watcher] of this.watchers) {
- await watcher.stop();
- }
-
- this.watchers.clear();
- }
-
- private log(message: string) {
- this._onDidLogMessage.fire({ type: 'trace', message: `[File Watcher (chokidar)] ` + message });
- }
-
- private debug(message: string) {
- this._onDidLogMessage.fire({ type: 'debug', message: `[File Watcher (chokidar)] ` + message });
- }
-
- private warn(message: string) {
- this._onDidLogMessage.fire({ type: 'warn', message: `[File Watcher (chokidar)] ` + message });
- }
-
- private error(message: string) {
- this._onDidLogMessage.fire({ type: 'error', message: `[File Watcher (chokidar)] ` + message });
- }
-}
-
-function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean {
- for (const request of requests) {
- if (request.path === path) {
- return false;
- }
-
- if (isEqualOrParent(path, request.path)) {
- if (!request.parsedPattern) {
- if (request.excludes && request.excludes.length > 0) {
- const pattern = `{${request.excludes.join(',')}}`;
- request.parsedPattern = parse(pattern);
- } else {
- request.parsedPattern = () => false;
- }
- }
-
- const relPath = path.substr(request.path.length + 1);
- if (!request.parsedPattern(relPath)) {
- return false;
- }
- }
- }
-
- return true;
-}
-
-/**
- * Normalizes a set of root paths by grouping by the most parent root path.
- * equests with Sub paths are skipped if they have the same ignored set as the parent.
- */
-export function normalizeRoots(requests: IWatchRequest[]): { [basePath: string]: IWatchRequest[] } {
- requests = requests.sort((r1, r2) => r1.path.localeCompare(r2.path));
-
- let prevRequest: IWatchRequest | null = null;
- const result: { [basePath: string]: IWatchRequest[] } = Object.create(null);
- for (const request of requests) {
- const basePath = request.path;
- const ignored = (request.excludes || []).sort();
- if (prevRequest && (isEqualOrParent(basePath, prevRequest.path))) {
- if (!isEqualIgnore(ignored, prevRequest.excludes)) {
- result[prevRequest.path].push({ path: basePath, excludes: ignored });
- }
- } else {
- prevRequest = { path: basePath, excludes: ignored };
- result[basePath] = [prevRequest];
- }
- }
-
- return result;
-}
-
-function isEqualRequests(r1: readonly IWatchRequest[], r2: readonly IWatchRequest[]) {
- return equals(r1, r2, (a, b) => a.path === b.path && isEqualIgnore(a.excludes, b.excludes));
-}
-
-function isEqualIgnore(i1: readonly string[], i2: readonly string[]) {
- return equals(i1, i2);
-}
diff --git a/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts
deleted file mode 100644
index 73e42892f70..00000000000
--- a/src/vs/platform/files/node/watcher/unix/test/chockidarWatcherService.test.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as assert from 'assert';
-import * as platform from 'vs/base/common/platform';
-import { IWatchRequest } from 'vs/platform/files/common/watcher';
-
-suite('Chokidar normalizeRoots', async () => {
-
- // Load `chokidarWatcherService` within the suite to prevent all tests
- // from failing to start if `chokidar` was not properly installed
- const { normalizeRoots } = await import('vs/platform/files/node/watcher/unix/chokidarWatcherService');
-
- function newRequest(basePath: string, ignored: string[] = []): IWatchRequest {
- return { path: basePath, excludes: ignored };
- }
-
- function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) {
- const requests = inputPaths.map(path => newRequest(path));
- const actual = normalizeRoots(requests);
- assert.deepStrictEqual(Object.keys(actual).sort(), expectedPaths);
- }
-
- function assertNormalizedRequests(inputRequests: IWatchRequest[], expectedRequests: { [path: string]: IWatchRequest[] }) {
- const actual = normalizeRoots(inputRequests);
- const actualPath = Object.keys(actual).sort();
- const expectedPaths = Object.keys(expectedRequests).sort();
- assert.deepStrictEqual(actualPath, expectedPaths);
- for (let path of actualPath) {
- let a = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
- let e = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
- assert.deepStrictEqual(a, e);
- }
- }
-
- test('should not impacts roots that don\'t overlap', () => {
- if (platform.isWindows) {
- assertNormalizedRootPath(['C:\\a'], ['C:\\a']);
- assertNormalizedRootPath(['C:\\a', 'C:\\b'], ['C:\\a', 'C:\\b']);
- assertNormalizedRootPath(['C:\\a', 'C:\\b', 'C:\\c\\d\\e'], ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']);
- } else {
- assertNormalizedRootPath(['/a'], ['/a']);
- assertNormalizedRootPath(['/a', '/b'], ['/a', '/b']);
- assertNormalizedRootPath(['/a', '/b', '/c/d/e'], ['/a', '/b', '/c/d/e']);
- }
- });
-
- test('should remove sub-folders of other roots', () => {
- if (platform.isWindows) {
- assertNormalizedRootPath(['C:\\a', 'C:\\a\\b'], ['C:\\a']);
- assertNormalizedRootPath(['C:\\a', 'C:\\b', 'C:\\a\\b'], ['C:\\a', 'C:\\b']);
- assertNormalizedRootPath(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b'], ['C:\\a', 'C:\\b']);
- assertNormalizedRootPath(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d'], ['C:\\a']);
- } else {
- assertNormalizedRootPath(['/a', '/a/b'], ['/a']);
- assertNormalizedRootPath(['/a', '/b', '/a/b'], ['/a', '/b']);
- assertNormalizedRootPath(['/b/a', '/a', '/b', '/a/b'], ['/a', '/b']);
- assertNormalizedRootPath(['/a', '/a/b', '/a/c/d'], ['/a']);
- assertNormalizedRootPath(['/a/c/d/e', '/a/b/d', '/a/c/d', '/a/c/e/f', '/a/b'], ['/a/b', '/a/c/d', '/a/c/e/f']);
- }
- });
-
- test('should remove duplicates', () => {
- if (platform.isWindows) {
- assertNormalizedRootPath(['C:\\a', 'C:\\a\\', 'C:\\a'], ['C:\\a']);
- } else {
- assertNormalizedRootPath(['/a', '/a/', '/a'], ['/a']);
- assertNormalizedRootPath(['/a', '/b', '/a/b'], ['/a', '/b']);
- assertNormalizedRootPath(['/b/a', '/a', '/b', '/a/b'], ['/a', '/b']);
- assertNormalizedRootPath(['/a', '/a/b', '/a/c/d'], ['/a']);
- }
- });
-
- test('nested requests', () => {
- let p1, p2, p3;
- if (platform.isWindows) {
- p1 = 'C:\\a';
- p2 = 'C:\\a\\b';
- p3 = 'C:\\a\\b\\c';
- } else {
- p1 = '/a';
- p2 = '/a/b';
- p3 = '/a/b/c';
- }
- const r1 = newRequest(p1, ['**/*.ts']);
- const r2 = newRequest(p2, ['**/*.js']);
- const r3 = newRequest(p3, ['**/*.ts']);
- assertNormalizedRequests([r1, r2], { [p1]: [r1, r2] });
- assertNormalizedRequests([r2, r1], { [p1]: [r1, r2] });
- assertNormalizedRequests([r1, r2, r3], { [p1]: [r1, r2, r3] });
- assertNormalizedRequests([r1, r3], { [p1]: [r1] });
- assertNormalizedRequests([r2, r3], { [p2]: [r2, r3] });
- });
-});
diff --git a/src/vs/platform/files/node/watcher/unix/watcher.ts b/src/vs/platform/files/node/watcher/unix/watcher.ts
deleted file mode 100644
index d8b67fb998a..00000000000
--- a/src/vs/platform/files/node/watcher/unix/watcher.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { Event } from 'vs/base/common/event';
-import { IDiskFileChange, ILogMessage, IWatchRequest } from 'vs/platform/files/common/watcher';
-
-export interface IWatcherOptions {
- pollingInterval?: number;
- usePolling?: boolean | string[]; // boolean or a set of glob patterns matching folders that need polling
- verboseLogging?: boolean;
-}
-
-export interface IWatcherService {
-
- readonly onDidChangeFile: Event;
- readonly onDidLogMessage: Event;
-
- init(options: IWatcherOptions): Promise;
-
- watch(paths: IWatchRequest[]): Promise;
- setVerboseLogging(enabled: boolean): Promise;
-
- stop(): Promise;
-}
diff --git a/src/vs/platform/files/node/watcher/unix/watcherApp.ts b/src/vs/platform/files/node/watcher/unix/watcherApp.ts
deleted file mode 100644
index c56a3bbc40f..00000000000
--- a/src/vs/platform/files/node/watcher/unix/watcherApp.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
-import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
-import { ChokidarWatcherService } from 'vs/platform/files/node/watcher/unix/chokidarWatcherService';
-
-const server = new Server('watcher');
-const service = new ChokidarWatcherService();
-server.registerChannel('watcher', ProxyChannel.fromService(service));
diff --git a/src/vs/platform/files/node/watcher/unix/watcherService.ts b/src/vs/platform/files/node/watcher/unix/watcherService.ts
deleted file mode 100644
index 9ce026a3f52..00000000000
--- a/src/vs/platform/files/node/watcher/unix/watcherService.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { FileAccess } from 'vs/base/common/network';
-import { getNextTickChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
-import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
-import { IWatcherOptions, IWatcherService } from 'vs/platform/files/node/watcher/unix/watcher';
-import { IDiskFileChange, ILogMessage, IWatchRequest, WatcherService } from 'vs/platform/files/common/watcher';
-
-/**
- * @deprecated
- */
-export class FileWatcher extends WatcherService {
-
- private static readonly MAX_RESTARTS = 5;
-
- private service: IWatcherService | undefined;
-
- private isDisposed = false;
- private restartCounter = 0;
-
- private requests: IWatchRequest[] | undefined = undefined;
-
- constructor(
- private readonly onDidFilesChange: (changes: IDiskFileChange[]) => void,
- private readonly onLogMessage: (msg: ILogMessage) => void,
- private verboseLogging: boolean,
- private readonly watcherOptions: IWatcherOptions = {}
- ) {
- super();
-
- this.startWatching();
- }
-
- private startWatching(): void {
- const client = this._register(new Client(
- FileAccess.asFileUri('bootstrap-fork', require).fsPath,
- {
- serverName: 'File Watcher (chokidar)',
- args: ['--type=watcherServiceChokidar'],
- env: {
- VSCODE_AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/unix/watcherApp',
- VSCODE_PIPE_LOGGING: 'true',
- VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client
- }
- }
- ));
-
- this._register(client.onDidProcessExit(() => {
- // our watcher app should never be completed because it keeps on watching. being in here indicates
- // that the watcher process died and we want to restart it here. we only do it a max number of times
- if (!this.isDisposed) {
- if (this.restartCounter <= FileWatcher.MAX_RESTARTS && this.requests) {
- this.error('terminated unexpectedly and is restarted again...');
- this.restartCounter++;
- this.startWatching();
- this.service?.watch(this.requests);
- } else {
- this.error('failed to start after retrying for some time, giving up. Please report this as a bug report!');
- }
- }
- }));
-
- // Initialize watcher
- this.service = ProxyChannel.toService(getNextTickChannel(client.getChannel('watcher')));
- this.service.init({ ...this.watcherOptions, verboseLogging: this.verboseLogging });
-
- // Wire in event handlers
- this._register(this.service.onDidChangeFile(e => !this.isDisposed && this.onDidFilesChange(e)));
- this._register(this.service.onDidLogMessage(e => this.onLogMessage(e)));
- }
-
- async setVerboseLogging(verboseLogging: boolean): Promise {
- this.verboseLogging = verboseLogging;
-
- if (!this.isDisposed) {
- await this.service?.setVerboseLogging(verboseLogging);
- }
- }
-
- error(message: string) {
- this.onLogMessage({ type: 'error', message: `[File Watcher (chokidar)] ${message}` });
- }
-
- async watch(requests: IWatchRequest[]): Promise {
- this.requests = requests;
-
- await this.service?.watch(requests);
- }
-
- override dispose(): void {
- this.isDisposed = true;
-
- super.dispose();
- }
-}
diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts
index 5799becc5af..b5a8635edec 100644
--- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts
+++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts
@@ -4,25 +4,25 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
+import { IndexedDB } from 'vs/base/browser/indexedDB';
import { bufferToReadable, bufferToStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { basename, joinPath } from 'vs/base/common/resources';
-import { assertIsDefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { flakySuite } from 'vs/base/test/common/testUtils';
-import { IIndexedDBFileSystemProvider, IndexedDB, INDEXEDDB_LOGS_OBJECT_STORE, INDEXEDDB_USERDATA_OBJECT_STORE } from 'vs/platform/files/browser/indexedDBFileSystemProvider';
+import { IndexedDBFileSystemProvider } from 'vs/platform/files/browser/indexedDBFileSystemProvider';
import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderErrorCode, FileType, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
-flakySuite('IndexedDB File Service', function () {
+flakySuite('IndexedDBFileSystemProvider', function () {
const logSchema = 'logs';
let service: FileService;
- let logFileProvider: IIndexedDBFileSystemProvider;
- let userdataFileProvider: IIndexedDBFileSystemProvider;
+ let logFileProvider: IndexedDBFileSystemProvider;
+ let userdataFileProvider: IndexedDBFileSystemProvider;
const testDir = '/';
const logfileURIFromPaths = (paths: string[]) => joinPath(URI.from({ scheme: logSchema, path: testDir }), ...paths);
@@ -67,11 +67,13 @@ flakySuite('IndexedDB File Service', function () {
service = new FileService(logService);
disposables.add(service);
- logFileProvider = assertIsDefined(await new IndexedDB().createFileSystemProvider(Schemas.file, INDEXEDDB_LOGS_OBJECT_STORE, false));
+ const indexedDB = await IndexedDB.create('vscode-web-db-test', 1, ['vscode-userdata-store', 'vscode-logs-store']);
+
+ logFileProvider = new IndexedDBFileSystemProvider(logSchema, indexedDB, 'vscode-logs-store', false);
disposables.add(service.registerProvider(logSchema, logFileProvider));
disposables.add(logFileProvider);
- userdataFileProvider = assertIsDefined(await new IndexedDB().createFileSystemProvider(logSchema, INDEXEDDB_USERDATA_OBJECT_STORE, true));
+ userdataFileProvider = new IndexedDBFileSystemProvider(Schemas.userData, indexedDB, 'vscode-userdata-store', true);
disposables.add(service.registerProvider(Schemas.userData, userdataFileProvider));
disposables.add(userdataFileProvider);
};
diff --git a/src/vs/platform/layout/browser/zIndexRegistry.ts b/src/vs/platform/layout/browser/zIndexRegistry.ts
index 1b0ffe29afa..c8e4e9cf837 100644
--- a/src/vs/platform/layout/browser/zIndexRegistry.ts
+++ b/src/vs/platform/layout/browser/zIndexRegistry.ts
@@ -64,7 +64,7 @@ class ZIndexRegistry {
this.zIndexMap.forEach((zIndex, name) => {
ruleBuilder += `${this.getVarName(name)}: ${zIndex};\n`;
});
- createCSSRule('*', ruleBuilder, this.styleSheet);
+ createCSSRule(':root', ruleBuilder, this.styleSheet);
}
}
diff --git a/src/vs/platform/sharedProcess/common/sharedProcessWorkerService.ts b/src/vs/platform/sharedProcess/common/sharedProcessWorkerService.ts
index c64dadbe270..9bbf80c574c 100644
--- a/src/vs/platform/sharedProcess/common/sharedProcessWorkerService.ts
+++ b/src/vs/platform/sharedProcess/common/sharedProcessWorkerService.ts
@@ -20,6 +20,32 @@ export interface ISharedProcessWorkerProcess {
type: string;
}
+export interface IOnDidTerminateSharedProcessWorkerProcess {
+
+ /**
+ * More information around how the shared process worker
+ * process terminated. Will be `undefined` in case the
+ * worker process was terminated normally via APIs
+ * and will be defined in case the worker process
+ * terminated on its own, either unexpectedly or
+ * because it finished.
+ */
+ reason?: ISharedProcessWorkerProcessExit;
+}
+
+export interface ISharedProcessWorkerProcessExit {
+
+ /**
+ * The shared process worker process exit code if known.
+ */
+ code?: number;
+
+ /**
+ * The shared process worker process exit signal if known.
+ */
+ signal?: string;
+}
+
export interface ISharedProcessWorkerConfiguration {
/**
@@ -77,8 +103,12 @@ export interface ISharedProcessWorkerService {
* the same process from one window. The intent of these workers is to be reused per
* window and the communication channel allows to dynamically update the processes
* after the fact.
+ *
+ * @returns a promise that resolves then the worker terminated. Provides more details
+ * about the termination that can be used to figure out if the termination was unexpected
+ * or not and whether the worker needs to be restarted.
*/
- createWorker(configuration: ISharedProcessWorkerConfiguration): Promise;
+ createWorker(configuration: ISharedProcessWorkerConfiguration): Promise;
/**
* Terminates the process for the provided configuration if any.
diff --git a/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerMain.ts b/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerMain.ts
index 5fa8f213f7b..03d803ce0f7 100644
--- a/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerMain.ts
+++ b/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerMain.ts
@@ -11,8 +11,9 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { deepClone } from 'vs/base/common/objects';
+import { withNullAsUndefined } from 'vs/base/common/types';
import { removeDangerousEnvVariables } from 'vs/base/node/processes';
-import { hash, ISharedProcessWorkerConfiguration } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
+import { hash, ISharedProcessWorkerConfiguration, ISharedProcessWorkerProcessExit } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerMessages, ISharedProcessToWorkerMessage, ISharedProcessWorkerEnvironment, IWorkerToSharedProcessMessage } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorker';
/**
@@ -75,10 +76,11 @@ class SharedProcessWorkerMain {
process.spawn();
// Handle self termination of the child process
- const listener = Event.once(process.onDidProcessSelfTerminate)(() => {
+ const listener = Event.once(process.onDidProcessSelfTerminate)(reason => {
send({
id: SharedProcessWorkerMessages.SelfTerminated,
- configuration
+ configuration,
+ message: JSON.stringify(reason)
});
});
@@ -108,7 +110,7 @@ class SharedProcessWorkerMain {
class SharedProcessWorkerProcess extends Disposable {
- private readonly _onDidProcessSelfTerminate = this._register(new Emitter());
+ private readonly _onDidProcessSelfTerminate = this._register(new Emitter());
readonly onDidProcessSelfTerminate = this._onDidProcessSelfTerminate.event;
private child: ChildProcess | undefined = undefined;
@@ -151,7 +153,10 @@ class SharedProcessWorkerProcess extends Disposable {
this.child = undefined;
- this._onDidProcessSelfTerminate.fire();
+ this._onDidProcessSelfTerminate.fire({
+ code: withNullAsUndefined(code),
+ signal: withNullAsUndefined(signal)
+ });
}));
const onMessageEmitter = this._register(new Emitter());
diff --git a/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts b/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts
index c239065bbd9..4f51ff07e2f 100644
--- a/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts
+++ b/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts
@@ -6,11 +6,11 @@
import { ipcRenderer } from 'electron';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
-import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { Disposable } from 'vs/base/common/lifecycle';
import { FileAccess } from 'vs/base/common/network';
import { generateUuid } from 'vs/base/common/uuid';
import { ILogService } from 'vs/platform/log/common/log';
-import { hash, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
+import { hash, IOnDidTerminateSharedProcessWorkerProcess, ISharedProcessWorkerConfiguration, ISharedProcessWorkerProcessExit, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerMessages, ISharedProcessToWorkerMessage, IWorkerToSharedProcessMessage } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorker';
export class SharedProcessWorkerService implements ISharedProcessWorkerService {
@@ -18,23 +18,25 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
declare readonly _serviceBrand: undefined;
private readonly workers = new Map>();
- private readonly processes = new Map();
+
+ private readonly processeDisposables = new Map void>();
+ private readonly processResolvers = new Map void>();
constructor(
@ILogService private readonly logService: ILogService
) {
}
- async createWorker(configuration: ISharedProcessWorkerConfiguration): Promise {
+ async createWorker(configuration: ISharedProcessWorkerConfiguration): Promise {
const workerLogId = `window: ${configuration.reply.windowId}, moduleId: ${configuration.process.moduleId}`;
this.logService.trace(`SharedProcess: createWorker (${workerLogId})`);
// Ensure to dispose any existing process for config
const configurationHash = hash(configuration);
- if (this.processes.has(configurationHash)) {
+ if (this.processeDisposables.has(configurationHash)) {
this.logService.warn(`SharedProcess: createWorker found an existing worker that will be terminated (${workerLogId})`);
- this.disposeWorker(configuration);
+ this.doDisposeWorker(configuration);
}
const cts = new CancellationTokenSource();
@@ -43,8 +45,8 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
let windowPort: MessagePort | undefined = undefined;
let workerPort: MessagePort | undefined = undefined;
- // Store as process for later disposal
- this.processes.set(configurationHash, toDisposable(() => {
+ // Store as process for termination support
+ this.processeDisposables.set(configurationHash, (reason?: ISharedProcessWorkerProcessExit) => {
// Signal to token
cts.dispose(true);
@@ -57,14 +59,27 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
workerPort?.close();
// Remove from processes
- this.processes.delete(configurationHash);
- }));
+ this.processeDisposables.delete(configurationHash);
+
+ // Release process resolvers if any
+ const processResolver = this.processResolvers.get(configurationHash);
+ if (processResolver) {
+ this.processResolvers.delete(configurationHash);
+ processResolver({ reason });
+ }
+ });
// Acquire a worker for the configuration
worker = await this.getOrCreateWebWorker(configuration);
+ // Keep a promise that will resolve in the future when the
+ // underlying process terminates.
+ const onDidTerminate = new Promise(resolve => {
+ this.processResolvers.set(configurationHash, resolve);
+ });
+
if (cts.token.isCancellationRequested) {
- return;
+ return onDidTerminate;
}
// Create a `MessageChannel` with 2 ports:
@@ -78,7 +93,7 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
await worker.spawn(configuration, workerPort, cts.token);
if (cts.token.isCancellationRequested) {
- return;
+ return onDidTerminate;
}
// We cannot just send the `MessagePort` through our protocol back
@@ -86,6 +101,8 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
// to send it through the main process back to the window.
this.logService.trace(`SharedProcess: createWorker sending message port back to window (${workerLogId})`);
ipcRenderer.postMessage('vscode:relaySharedProcessWorkerMessageChannel', configuration, [windowPort]);
+
+ return onDidTerminate;
}
private getOrCreateWebWorker(configuration: ISharedProcessWorkerConfiguration): Promise {
@@ -103,11 +120,10 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
const sharedProcessWorker = new SharedProcessWebWorker(configuration.process.type, this.logService);
webWorkerPromise = sharedProcessWorker.init();
- // Make sure to run through our normal
- // `disposeWorker` call when the process
- // terminates by itself.
- sharedProcessWorker.onDidProcessSelfTerminate(configuration => {
- this.disposeWorker(configuration);
+ // Make sure to run through our normal `disposeWorker` call
+ // when the process terminates by itself.
+ sharedProcessWorker.onDidProcessSelfTerminate(({ configuration, reason }) => {
+ this.doDisposeWorker(configuration, reason);
});
this.workers.set(configuration.process.moduleId, webWorkerPromise);
@@ -117,18 +133,22 @@ export class SharedProcessWorkerService implements ISharedProcessWorkerService {
}
async disposeWorker(configuration: ISharedProcessWorkerConfiguration): Promise {
- const processDisposable = this.processes.get(hash(configuration));
+ return this.doDisposeWorker(configuration);
+ }
+
+ private doDisposeWorker(configuration: ISharedProcessWorkerConfiguration, reason?: ISharedProcessWorkerProcessExit): void {
+ const processDisposable = this.processeDisposables.get(hash(configuration));
if (processDisposable) {
this.logService.trace(`SharedProcess: disposeWorker (window: ${configuration.reply.windowId}, moduleId: ${configuration.process.moduleId})`);
- processDisposable.dispose();
+ processDisposable(reason);
}
}
}
class SharedProcessWebWorker extends Disposable {
- private readonly _onDidProcessSelfTerminate = this._register(new Emitter());
+ private readonly _onDidProcessSelfTerminate = this._register(new Emitter<{ configuration: ISharedProcessWorkerConfiguration, reason: ISharedProcessWorkerProcessExit }>());
readonly onDidProcessSelfTerminate = this._onDidProcessSelfTerminate.event;
private readonly workerReady: Promise = this.doInit();
@@ -186,8 +206,8 @@ class SharedProcessWebWorker extends Disposable {
// Lifecycle: self termination
case SharedProcessWorkerMessages.SelfTerminated:
- if (configuration) {
- this._onDidProcessSelfTerminate.fire(configuration);
+ if (configuration && message) {
+ this._onDidProcessSelfTerminate.fire({ configuration, reason: JSON.parse(message) });
}
break;
diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts
index c196c2d918b..36cd750dc0c 100644
--- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts
+++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts
@@ -92,6 +92,10 @@ export class SharedProcess extends Disposable implements ISharedProcess {
const disposables = new DisposableStore();
const disposeWorker = (reason: string) => {
+ if (!this.isAlive()) {
+ return; // the shared process is already gone, no need to dispose anything
+ }
+
this.logService.trace(`SharedProcess: disposing worker (reason: '${reason}')`, configuration);
// Only once!
@@ -152,14 +156,13 @@ export class SharedProcess extends Disposable implements ISharedProcess {
}
private send(channel: string, ...args: any[]): void {
- const window = this.window;
- if (!window || window.isDestroyed() || window.webContents.isDestroyed()) {
+ if (!this.isAlive()) {
this.logService.warn(`Sending IPC message to channel '${channel}' for shared process window that is destroyed`);
return;
}
try {
- window.webContents.send(channel, ...args);
+ this.window?.webContents.send(channel, ...args);
} catch (error) {
this.logService.warn(`Error sending IPC message to channel '${channel}' of shared process: ${toErrorMessage(error)}`);
}
@@ -305,4 +308,13 @@ export class SharedProcess extends Disposable implements ISharedProcess {
isVisible(): boolean {
return this.window?.isVisible() ?? false;
}
+
+ private isAlive(): boolean {
+ const window = this.window;
+ if (!window) {
+ return false;
+ }
+
+ return !window.isDestroyed() && !window.webContents.isDestroyed();
+ }
}
diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts
index a6a7466efa4..6c60de2fb76 100644
--- a/src/vs/platform/storage/common/storage.ts
+++ b/src/vs/platform/storage/common/storage.ts
@@ -6,6 +6,7 @@
import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
import { Disposable, dispose, MutableDisposable } from 'vs/base/common/lifecycle';
+import { mark } from 'vs/base/common/performance';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { InMemoryStorageDatabase, IStorage, Storage } from 'vs/base/parts/storage/common/storage';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -271,8 +272,14 @@ export abstract class AbstractStorageService extends Disposable implements IStor
if (!this.initializationPromise) {
this.initializationPromise = (async () => {
- // Ask subclasses to initialize storage
- await this.doInitialize();
+ // Init all storage locations
+ mark('code/willInitStorage');
+ try {
+ // Ask subclasses to initialize storage
+ await this.doInitialize();
+ } finally {
+ mark('code/didInitStorage');
+ }
// On some OS we do not get enough time to persist state on shutdown (e.g. when
// Windows restarts after applying updates). In other cases, VSCode might crash,
diff --git a/src/vs/platform/storage/electron-sandbox/storageService.ts b/src/vs/platform/storage/electron-sandbox/storageService.ts
index 6e2ac17172b..95c29dbaab3 100644
--- a/src/vs/platform/storage/electron-sandbox/storageService.ts
+++ b/src/vs/platform/storage/electron-sandbox/storageService.ts
@@ -5,7 +5,6 @@
import { Promises } from 'vs/base/common/async';
import { MutableDisposable } from 'vs/base/common/lifecycle';
-import { mark } from 'vs/base/common/performance';
import { joinPath } from 'vs/base/common/resources';
import { IStorage, Storage } from 'vs/base/parts/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -67,17 +66,11 @@ export class NativeStorageService extends AbstractStorageService {
}
protected async doInitialize(): Promise {
-
// Init all storage locations
- mark('code/willInitStorage');
- try {
- await Promises.settled([
- this.globalStorage.init(),
- this.workspaceStorage?.init() ?? Promise.resolve()
- ]);
- } finally {
- mark('code/didInitStorage');
- }
+ await Promises.settled([
+ this.globalStorage.init(),
+ this.workspaceStorage?.init() ?? Promise.resolve()
+ ]);
}
protected getStorage(scope: StorageScope): IStorage | undefined {
diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts
index 1ce90fd63da..cec826e949f 100644
--- a/src/vs/platform/telemetry/node/appInsightsAppender.ts
+++ b/src/vs/platform/telemetry/node/appInsightsAppender.ts
@@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { mixin } from 'vs/base/common/objects';
import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
-async function getClient(aiKey: string): Promise {
+async function getClient(aiKey: string, testCollector: boolean): Promise {
const appInsights = await import('applicationinsights');
let client: TelemetryClient;
if (appInsights.defaultClient) {
@@ -29,7 +29,7 @@ async function getClient(aiKey: string): Promise {
}
if (aiKey.indexOf('AIF-') === 0) {
- client.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1';
+ client.config.endpointUrl = testCollector ? 'https://mobile.events.data.microsoft.com/OneCollector/1.0' : 'https://vortex.data.microsoft.com/collect/v1';
}
return client;
}
@@ -44,6 +44,8 @@ export class AppInsightsAppender implements ITelemetryAppender {
private _eventPrefix: string,
private _defaultData: { [key: string]: any } | null,
aiKeyOrClientFactory: string | (() => TelemetryClient), // allow factory function for testing
+ private readonly testCollector?: boolean,
+ private readonly mirrored?: boolean
) {
if (!this._defaultData) {
this._defaultData = Object.create(null);
@@ -68,7 +70,7 @@ export class AppInsightsAppender implements ITelemetryAppender {
}
if (!this._asyncAIClient) {
- this._asyncAIClient = getClient(this._aiClient);
+ this._asyncAIClient = getClient(this._aiClient, this.testCollector ?? false);
}
this._asyncAIClient.then(
@@ -89,6 +91,10 @@ export class AppInsightsAppender implements ITelemetryAppender {
data = mixin(data, this._defaultData);
data = validateTelemetryData(data);
+ if (this.testCollector) {
+ data.properties['common.useragent'] = this.mirrored ? 'mirror-collector++' : 'collector++';
+ }
+
this._withAIClient((aiClient) => aiClient.trackEvent({
name: this._eventPrefix + '/' + eventName,
properties: data.properties,
diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts
index 833f556f807..1770760c793 100644
--- a/src/vs/platform/terminal/common/terminal.ts
+++ b/src/vs/platform/terminal/common/terminal.ts
@@ -25,6 +25,9 @@ export const enum TerminalSettingId {
AutomationShellLinux = 'terminal.integrated.automationShell.linux',
AutomationShellMacOs = 'terminal.integrated.automationShell.osx',
AutomationShellWindows = 'terminal.integrated.automationShell.windows',
+ AutomationProfileLinux = 'terminal.integrated.automationProfile.linux',
+ AutomationProfileMacOs = 'terminal.integrated.automationProfile.osx',
+ AutomationProfileWindows = 'terminal.integrated.automationProfile.windows',
ShellArgsLinux = 'terminal.integrated.shellArgs.linux',
ShellArgsMacOs = 'terminal.integrated.shellArgs.osx',
ShellArgsWindows = 'terminal.integrated.shellArgs.windows',
@@ -181,7 +184,12 @@ export const IPtyService = createDecorator('ptyService');
export const enum ProcessPropertyType {
Cwd = 'cwd',
InitialCwd = 'initialCwd',
- FixedDimensions = 'fixedDimensions'
+ FixedDimensions = 'fixedDimensions',
+ Title = 'title',
+ ShellType = 'shellType',
+ HasChildProcesses = 'hasChildProcesses',
+ ResolvedShellLaunchConfig = 'resolvedShellLaunchConfig',
+ OverrideDimensions = 'overrideDimensions'
}
export interface IProcessProperty {
@@ -192,7 +200,12 @@ export interface IProcessProperty {
export interface IProcessPropertyMap {
[ProcessPropertyType.Cwd]: string,
[ProcessPropertyType.InitialCwd]: string,
- [ProcessPropertyType.FixedDimensions]: IFixedTerminalDimensions
+ [ProcessPropertyType.FixedDimensions]: IFixedTerminalDimensions,
+ [ProcessPropertyType.Title]: string
+ [ProcessPropertyType.ShellType]: TerminalShellType | undefined,
+ [ProcessPropertyType.HasChildProcesses]: boolean,
+ [ProcessPropertyType.ResolvedShellLaunchConfig]: IShellLaunchConfig,
+ [ProcessPropertyType.OverrideDimensions]: ITerminalDimensionsOverride | undefined
}
export interface IFixedTerminalDimensions {
@@ -218,17 +231,12 @@ export interface IPtyService {
readonly onPtyHostRequestResolveVariables?: Event;
readonly onProcessData: Event<{ id: number, event: IProcessDataEvent | string }>;
- readonly onProcessExit: Event<{ id: number, event: number | undefined }>;
- readonly onProcessReady: Event<{ id: number, event: { pid: number, cwd: string, capabilities: ProcessCapability[] } }>;
- readonly onProcessTitleChanged: Event<{ id: number, event: string }>;
- readonly onProcessShellTypeChanged: Event<{ id: number, event: TerminalShellType }>;
- readonly onProcessOverrideDimensions: Event<{ id: number, event: ITerminalDimensionsOverride | undefined }>;
- readonly onProcessResolvedShellLaunchConfig: Event<{ id: number, event: IShellLaunchConfig }>;
+ readonly onProcessReady: Event<{ id: number, event: IProcessReadyEvent }>;
readonly onProcessReplay: Event<{ id: number, event: IPtyHostProcessReplayEvent }>;
readonly onProcessOrphanQuestion: Event<{ id: number }>;
readonly onDidRequestDetach: Event<{ requestId: number, workspaceId: string, instanceId: number }>;
- readonly onProcessDidChangeHasChildProcesses: Event<{ id: number, event: boolean }>;
readonly onDidChangeProperty: Event<{ id: number, property: IProcessProperty }>
+ readonly onProcessExit: Event<{ id: number, event: number | undefined }>;
restartPtyHost?(): Promise;
shutdownAll?(): Promise;
@@ -527,14 +535,9 @@ export interface ITerminalChildProcess {
capabilities: ProcessCapability[];
onProcessData: Event;
- onProcessExit: Event;
onProcessReady: Event;
- onProcessTitleChanged: Event;
- onProcessShellTypeChanged: Event;
- onProcessOverrideDimensions?: Event;
- onProcessResolvedShellLaunchConfig?: Event;
- onDidChangeHasChildProcesses?: Event;
onDidChangeProperty: Event>;
+ onProcessExit: Event;
/**
* Starts the process.
diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
index af012cffc67..9ea5b9acee8 100644
--- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
+++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
@@ -73,6 +73,9 @@ const terminalProfileSchema: IJSONSchema = {
const shellDeprecationMessageLinux = localize('terminal.integrated.shell.linux.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.linux#`', '`#terminal.integrated.defaultProfile.linux#`');
const shellDeprecationMessageOsx = localize('terminal.integrated.shell.osx.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.osx#`', '`#terminal.integrated.defaultProfile.osx#`');
const shellDeprecationMessageWindows = localize('terminal.integrated.shell.windows.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.windows#`', '`#terminal.integrated.defaultProfile.windows#`');
+const automationShellDeprecationMessageLinux = localize('terminal.integrated.automationShell.linux.deprecation', "This is deprecated, the new recommended way to configure your automation shell is by creating a terminal automation profile with {0}. This will currently take priority over the new automation profile settings but that will change in the future.", '`#terminal.integrated.automationProfile.linux#`');
+const automationShellDeprecationMessageOsx = localize('terminal.integrated.automationShell.osx.deprecation', "This is deprecated, the new recommended way to configure your automation shell is by creating a terminal automation profile with {0}. This will currently take priority over the new automation profile settings but that will change in the future.", '`#terminal.integrated.automationProfile.osx#`');
+const automationShellDeprecationMessageWindows = localize('terminal.integrated.automationShell.windows.deprecation', "This is deprecated, the new recommended way to configure your automation shell is by creating a terminal automation profile with {0}. This will currently take priority over the new automation profile settings but that will change in the future.", '`#terminal.integrated.automationProfile.windows#`');
const terminalPlatformConfiguration: IConfigurationNode = {
id: 'terminal',
@@ -87,7 +90,8 @@ const terminalPlatformConfiguration: IConfigurationNode = {
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`'),
type: ['string', 'null'],
- default: null
+ default: null,
+ markdownDeprecationMessage: automationShellDeprecationMessageLinux
},
[TerminalSettingId.AutomationShellMacOs]: {
restricted: true,
@@ -96,7 +100,8 @@ const terminalPlatformConfiguration: IConfigurationNode = {
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`'),
type: ['string', 'null'],
- default: null
+ default: null,
+ markdownDeprecationMessage: automationShellDeprecationMessageOsx
},
[TerminalSettingId.AutomationShellWindows]: {
restricted: true,
@@ -105,7 +110,38 @@ const terminalPlatformConfiguration: IConfigurationNode = {
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`'),
type: ['string', 'null'],
- default: null
+ default: null,
+ markdownDeprecationMessage: automationShellDeprecationMessageWindows
+ },
+ [TerminalSettingId.AutomationProfileLinux]: {
+ restricted: true,
+ markdownDescription: localize('terminal.integrated.automationProfile.linux', "The terminal profile to use on Linux for automation-related terminal usage like tasks and debug. This setting will currently be ignored if {0} is set.", '#terminal.integrated.automationShell.linux#'),
+ type: ['object', 'null'],
+ default: null,
+ 'anyOf': [
+ { type: 'null' },
+ terminalProfileSchema
+ ]
+ },
+ [TerminalSettingId.AutomationProfileMacOs]: {
+ restricted: true,
+ description: localize('terminal.integrated.automationProfile.osx', "The terminal profile to use on macOS for automation-related terminal usage like tasks and debug. This setting will currently be ignored if {0} is set.", '#terminal.integrated.automationShell.osx#'),
+ type: ['object', 'null'],
+ default: null,
+ 'anyOf': [
+ { type: 'null' },
+ terminalProfileSchema
+ ]
+ },
+ [TerminalSettingId.AutomationProfileWindows]: {
+ restricted: true,
+ description: localize('terminal.integrated.automationProfile.windows', "The terminal profile to use for automation-related terminal usage like tasks and debug. This setting will currently be ignored if {0} is set.", '#terminal.integrated.automationShell.windows#'),
+ type: ['object', 'null'],
+ default: null,
+ 'anyOf': [
+ { type: 'null' },
+ terminalProfileSchema
+ ]
},
[TerminalSettingId.ShellLinux]: {
restricted: true,
diff --git a/src/vs/platform/terminal/common/terminalProfiles.ts b/src/vs/platform/terminal/common/terminalProfiles.ts
index 63f49db9bd1..970db1c3b28 100644
--- a/src/vs/platform/terminal/common/terminalProfiles.ts
+++ b/src/vs/platform/terminal/common/terminalProfiles.ts
@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
-import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/terminal/common/terminal';
+import { URI, UriComponents } from 'vs/base/common/uri';
+import { IExtensionTerminalProfile, ITerminalProfile, TerminalIcon } from 'vs/platform/terminal/common/terminal';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export function createProfileSchemaEnums(detectedProfiles: ITerminalProfile[], extensionProfiles?: readonly IExtensionTerminalProfile[]): {
@@ -56,3 +57,60 @@ function createExtensionProfileDescription(profile: IExtensionTerminalProfile):
let description = `$(${ThemeIcon.isThemeIcon(profile.icon) ? profile.icon.id : profile.icon ? profile.icon : Codicon.terminal.id}) ${profile.title}\n- extensionIdenfifier: ${profile.extensionIdentifier}`;
return description;
}
+
+
+export function terminalProfileArgsMatch(args1: string | string[] | undefined, args2: string | string[] | undefined): boolean {
+ if (!args1 && !args2) {
+ return true;
+ } else if (typeof args1 === 'string' && typeof args2 === 'string') {
+ return args1 === args2;
+ } else if (Array.isArray(args1) && Array.isArray(args2)) {
+ if (args1.length !== args2.length) {
+ return false;
+ }
+ for (let i = 0; i < args1.length; i++) {
+ if (args1[i] !== args2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+export function terminalIconsEqual(iconOne?: TerminalIcon, iconTwo?: TerminalIcon): boolean {
+ if (!iconOne && !iconTwo) {
+ return true;
+ } else if (!iconOne || !iconTwo) {
+ return false;
+ }
+
+ if (ThemeIcon.isThemeIcon(iconOne) && ThemeIcon.isThemeIcon(iconTwo)) {
+ return iconOne.id === iconTwo.id && iconOne.color === iconTwo.color;
+ }
+ if (typeof iconOne === 'object' && iconOne && 'light' in iconOne && 'dark' in iconOne
+ && typeof iconTwo === 'object' && iconTwo && 'light' in iconTwo && 'dark' in iconTwo) {
+ const castedIcon = (iconOne as { light: unknown, dark: unknown });
+ const castedIconTwo = (iconTwo as { light: unknown, dark: unknown });
+ if ((URI.isUri(castedIcon.light) || isUriComponents(castedIcon.light)) && (URI.isUri(castedIcon.dark) || isUriComponents(castedIcon.dark))
+ && (URI.isUri(castedIconTwo.light) || isUriComponents(castedIconTwo.light)) && (URI.isUri(castedIconTwo.dark) || isUriComponents(castedIconTwo.dark))) {
+ return castedIcon.light.path === castedIconTwo.light.path && castedIcon.dark.path === castedIconTwo.dark.path;
+ }
+ }
+ if ((URI.isUri(iconOne) && URI.isUri(iconTwo)) || (isUriComponents(iconOne) || isUriComponents(iconTwo))) {
+ const castedIcon = (iconOne as { scheme: unknown, path: unknown });
+ const castedIconTwo = (iconTwo as { scheme: unknown, path: unknown });
+ return castedIcon.path === castedIconTwo.path && castedIcon.scheme === castedIconTwo.scheme;
+ }
+
+ return false;
+}
+
+
+export function isUriComponents(thing: unknown): thing is UriComponents {
+ if (!thing) {
+ return false;
+ }
+ return typeof (thing).path === 'string' &&
+ typeof (thing).scheme === 'string';
+}
diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts
index e13e7730b6d..3c44bae9a5b 100644
--- a/src/vs/platform/terminal/node/ptyHostService.ts
+++ b/src/vs/platform/terminal/node/ptyHostService.ts
@@ -17,7 +17,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RequestStore } from 'vs/platform/terminal/common/requestStore';
-import { HeartbeatConstants, IHeartbeatService, IProcessDataEvent, IPtyService, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, TerminalIcon, TerminalIpcChannels, IProcessProperty, TerminalShellType, TitleEventSource, ProcessPropertyType, ProcessCapability, IProcessPropertyMap, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
+import { HeartbeatConstants, IHeartbeatService, IProcessDataEvent, IPtyService, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, TerminalIcon, TerminalIpcChannels, IProcessProperty, TitleEventSource, ProcessPropertyType, ProcessCapability, IProcessPropertyMap, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { registerTerminalPlatformConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
import { detectAvailableProfiles } from 'vs/platform/terminal/node/terminalProfiles';
@@ -64,28 +64,18 @@ export class PtyHostService extends Disposable implements IPtyService {
private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>());
readonly onProcessData = this._onProcessData.event;
- private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>());
- readonly onProcessExit = this._onProcessExit.event;
private readonly _onProcessReady = this._register(new Emitter<{ id: number, event: { pid: number, cwd: string, capabilities: ProcessCapability[] } }>());
readonly onProcessReady = this._onProcessReady.event;
private readonly _onProcessReplay = this._register(new Emitter<{ id: number, event: IPtyHostProcessReplayEvent }>());
readonly onProcessReplay = this._onProcessReplay.event;
- private readonly _onProcessTitleChanged = this._register(new Emitter<{ id: number, event: string }>());
- readonly onProcessTitleChanged = this._onProcessTitleChanged.event;
- private readonly _onProcessShellTypeChanged = this._register(new Emitter<{ id: number, event: TerminalShellType }>());
- readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
- private readonly _onProcessOverrideDimensions = this._register(new Emitter<{ id: number, event: ITerminalDimensionsOverride | undefined }>());
- readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event;
- private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<{ id: number, event: IShellLaunchConfig }>());
- readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event;
private readonly _onProcessOrphanQuestion = this._register(new Emitter<{ id: number }>());
readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number, workspaceId: string, instanceId: number }>());
readonly onDidRequestDetach = this._onDidRequestDetach.event;
- private readonly _onProcessDidChangeHasChildProcesses = this._register(new Emitter<{ id: number, event: boolean }>());
- readonly onProcessDidChangeHasChildProcesses = this._onProcessDidChangeHasChildProcesses.event;
private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number, property: IProcessProperty }>());
readonly onDidChangeProperty = this._onDidChangeProperty.event;
+ private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>());
+ readonly onProcessExit = this._onProcessExit.event;
constructor(
private readonly _reconnectConstants: IReconnectConstants,
@@ -202,13 +192,8 @@ export class PtyHostService extends Disposable implements IPtyService {
// Create proxy and forward events
const proxy = ProxyChannel.toService(client.getChannel(TerminalIpcChannels.PtyHost));
this._register(proxy.onProcessData(e => this._onProcessData.fire(e)));
- this._register(proxy.onProcessExit(e => this._onProcessExit.fire(e)));
this._register(proxy.onProcessReady(e => this._onProcessReady.fire(e)));
- this._register(proxy.onProcessTitleChanged(e => this._onProcessTitleChanged.fire(e)));
- this._register(proxy.onProcessShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e)));
- this._register(proxy.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e)));
- this._register(proxy.onProcessResolvedShellLaunchConfig(e => this._onProcessResolvedShellLaunchConfig.fire(e)));
- this._register(proxy.onProcessDidChangeHasChildProcesses(e => this._onProcessDidChangeHasChildProcesses.fire(e)));
+ this._register(proxy.onProcessExit(e => this._onProcessExit.fire(e)));
this._register(proxy.onDidChangeProperty(e => this._onDidChangeProperty.fire(e)));
this._register(proxy.onProcessReplay(e => this._onProcessReplay.fire(e)));
this._register(proxy.onProcessOrphanQuestion(e => this._onProcessOrphanQuestion.fire(e)));
diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts
index 5d2ae38fc73..16c68b790de 100644
--- a/src/vs/platform/terminal/node/ptyService.ts
+++ b/src/vs/platform/terminal/node/ptyService.ts
@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { getSystemShell } from 'vs/base/node/shell';
import { ILogService } from 'vs/platform/log/common/log';
import { RequestStore } from 'vs/platform/terminal/common/requestStore';
-import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TerminalShellType, TitleEventSource, ProcessPropertyType, ProcessCapability, IProcessPropertyMap, IFixedTerminalDimensions } from 'vs/platform/terminal/common/terminal';
+import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, ProcessCapability } from 'vs/platform/terminal/common/terminal';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment';
import { Terminal as XtermTerminal } from 'xterm-headless';
@@ -44,24 +44,14 @@ export class PtyService extends Disposable implements IPtyService {
readonly onProcessData = this._onProcessData.event;
private readonly _onProcessReplay = this._register(new Emitter<{ id: number, event: IPtyHostProcessReplayEvent }>());
readonly onProcessReplay = this._onProcessReplay.event;
- private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>());
- readonly onProcessExit = this._onProcessExit.event;
private readonly _onProcessReady = this._register(new Emitter<{ id: number, event: { pid: number, cwd: string, capabilities: ProcessCapability[] } }>());
readonly onProcessReady = this._onProcessReady.event;
- private readonly _onProcessTitleChanged = this._register(new Emitter<{ id: number, event: string }>());
- readonly onProcessTitleChanged = this._onProcessTitleChanged.event;
- private readonly _onProcessShellTypeChanged = this._register(new Emitter<{ id: number, event: TerminalShellType }>());
- readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
- private readonly _onProcessOverrideDimensions = this._register(new Emitter<{ id: number, event: ITerminalDimensionsOverride | undefined }>());
- readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event;
- private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<{ id: number, event: IShellLaunchConfig }>());
- readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event;
+ private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>());
+ readonly onProcessExit = this._onProcessExit.event;
private readonly _onProcessOrphanQuestion = this._register(new Emitter<{ id: number }>());
readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number, workspaceId: string, instanceId: number }>());
readonly onDidRequestDetach = this._onDidRequestDetach.event;
- private readonly _onProcessDidChangeHasChildProcesses = this._register(new Emitter<{ id: number, event: boolean }>());
- readonly onProcessDidChangeHasChildProcesses = this._onProcessDidChangeHasChildProcesses.event;
private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number, property: IProcessProperty }>());
readonly onDidChangeProperty = this._onDidChangeProperty.event;
@@ -195,31 +185,20 @@ export class PtyService extends Disposable implements IPtyService {
const id = ++this._lastPtyId;
const process = new TerminalProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty, this._logService);
process.onProcessData(event => this._onProcessData.fire({ id, event }));
- process.onProcessExit(event => this._onProcessExit.fire({ id, event }));
- if (process.onProcessOverrideDimensions) {
- process.onProcessOverrideDimensions(event => this._onProcessOverrideDimensions.fire({ id, event }));
- }
- if (process.onProcessResolvedShellLaunchConfig) {
- process.onProcessResolvedShellLaunchConfig(event => this._onProcessResolvedShellLaunchConfig.fire({ id, event }));
- }
- if (process.onDidChangeHasChildProcesses) {
- process.onDidChangeHasChildProcesses(event => this._onProcessDidChangeHasChildProcesses.fire({ id, event }));
- }
const processLaunchOptions: IPersistentTerminalProcessLaunchOptions = {
env,
executableEnv,
windowsEnableConpty
};
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.fixedDimensions);
- process.onProcessExit(() => {
+ process.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
+ process.onProcessExit(event => {
persistentProcess.dispose();
this._ptys.delete(id);
+ this._onProcessExit.fire({ id, event });
});
- process.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
persistentProcess.onProcessReplay(event => this._onProcessReplay.fire({ id, event }));
persistentProcess.onProcessReady(event => this._onProcessReady.fire({ id, event }));
- persistentProcess.onProcessTitleChanged(event => this._onProcessTitleChanged.fire({ id, event }));
- persistentProcess.onProcessShellTypeChanged(event => this._onProcessShellTypeChanged.fire({ id, event }));
persistentProcess.onProcessOrphanQuestion(() => this._onProcessOrphanQuestion.fire({ id }));
persistentProcess.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
this._ptys.set(id, persistentProcess);
@@ -428,12 +407,6 @@ export class PersistentTerminalProcess extends Disposable {
readonly onProcessReplay = this._onProcessReplay.event;
private readonly _onProcessReady = this._register(new Emitter());
readonly onProcessReady = this._onProcessReady.event;
- private readonly _onProcessTitleChanged = this._register(new Emitter());
- readonly onProcessTitleChanged = this._onProcessTitleChanged.event;
- private readonly _onProcessShellTypeChanged = this._register(new Emitter());
- readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
- private readonly _onProcessOverrideDimensions = this._register(new Emitter());
- readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event;
private readonly _onProcessData = this._register(new Emitter());
readonly onProcessData = this._onProcessData.event;
private readonly _onProcessOrphanQuestion = this._register(new Emitter());
@@ -511,20 +484,19 @@ export class PersistentTerminalProcess extends Disposable {
this._logService.info(`Persistent process "${this._persistentProcessId}": The short reconnection grace time of ${printTime(reconnectConstants.shortGraceTime)} has expired, shutting down pid ${this._pid}`);
this.shutdown(true);
}, reconnectConstants.shortGraceTime));
-
+ this._register(this._terminalProcess.onProcessExit(() => this._bufferer.stopBuffering(this._persistentProcessId)));
this._register(this._terminalProcess.onProcessReady(e => {
this._pid = e.pid;
this._cwd = e.cwd;
this._onProcessReady.fire(e);
}));
- this._register(this._terminalProcess.onProcessTitleChanged(e => this._onProcessTitleChanged.fire(e)));
- this._register(this._terminalProcess.onProcessShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e)));
- this._register(this._terminalProcess.onDidChangeProperty(e => this._onDidChangeProperty.fire(e)));
+ this._register(this._terminalProcess.onDidChangeProperty(e => {
+ this._onDidChangeProperty.fire(e);
+ }));
// Data buffering to reduce the amount of messages going to the renderer
this._bufferer = new TerminalDataBufferer((_, data) => this._onProcessData.fire(data));
this._register(this._bufferer.startBuffering(this._persistentProcessId, this._terminalProcess.onProcessData));
- this._register(this._terminalProcess.onProcessExit(() => this._bufferer.stopBuffering(this._persistentProcessId)));
// Data recording for reconnect
this._register(this.onProcessData(e => this._serializer.handleData(e)));
@@ -579,8 +551,8 @@ export class PersistentTerminalProcess extends Disposable {
}
} else {
this._onProcessReady.fire({ pid: this._pid, cwd: this._cwd, capabilities: this._terminalProcess.capabilities, requiresWindowsMode: isWindows && getWindowsBuildNumber() < 21376 });
- this._onProcessTitleChanged.fire(this._terminalProcess.currentTitle);
- this._onProcessShellTypeChanged.fire(this._terminalProcess.shellType);
+ this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: this._terminalProcess.currentTitle });
+ this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: this._terminalProcess.shellType });
this.triggerReplay();
}
return undefined;
diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts
index 8fa92e037ed..af6a0f85403 100644
--- a/src/vs/platform/terminal/node/terminalProcess.ts
+++ b/src/vs/platform/terminal/node/terminalProcess.ts
@@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri';
import { Promises } from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { ILogService } from 'vs/platform/log/common/log';
-import { FlowControlConstants, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap as IProcessPropertyMap, ProcessPropertyType, TerminalShellType, ProcessCapability } from 'vs/platform/terminal/common/terminal';
+import { FlowControlConstants, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap as IProcessPropertyMap, ProcessPropertyType, TerminalShellType, ProcessCapability, IProcessReadyEvent } from 'vs/platform/terminal/common/terminal';
import { ChildProcessMonitor } from 'vs/platform/terminal/node/childProcessMonitor';
import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment';
import { WindowsShellHelper } from 'vs/platform/terminal/node/windowsShellHelper';
@@ -79,7 +79,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
private _properties: IProcessPropertyMap = {
cwd: '',
initialCwd: '',
- fixedDimensions: { cols: undefined, rows: undefined }
+ fixedDimensions: { cols: undefined, rows: undefined },
+ title: '',
+ shellType: undefined,
+ hasChildProcesses: true,
+ resolvedShellLaunchConfig: {},
+ overrideDimensions: undefined
};
private static _lastKillOrStart = 0;
private _exitCode: number | undefined;
@@ -110,21 +115,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
private readonly _onProcessData = this._register(new Emitter());
readonly onProcessData = this._onProcessData.event;
- private readonly _onProcessExit = this._register(new Emitter());
- readonly onProcessExit = this._onProcessExit.event;
private readonly _onProcessReady = this._register(new Emitter());
readonly onProcessReady = this._onProcessReady.event;
- private readonly _onProcessTitleChanged = this._register(new Emitter());
- readonly onProcessTitleChanged = this._onProcessTitleChanged.event;
- private readonly _onProcessShellTypeChanged = this._register(new Emitter());
- readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
- private readonly _onDidChangeHasChildProcesses = this._register(new Emitter());
- readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event;
private readonly _onDidChangeProperty = this._register(new Emitter>());
readonly onDidChangeProperty = this._onDidChangeProperty.event;
-
- onProcessOverrideDimensions?: Event | undefined;
- onProcessResolvedShellLaunchConfig?: Event | undefined;
+ private readonly _onProcessExit = this._register(new Emitter());
+ readonly onProcessExit = this._onProcessExit.event;
constructor(
readonly shellLaunchConfig: IShellLaunchConfig,
@@ -178,8 +174,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
// WindowsShellHelper is used to fetch the process title and shell type
this.onProcessReady(e => {
this._windowsShellHelper = this._register(new WindowsShellHelper(e.pid));
- this._register(this._windowsShellHelper.onShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e)));
- this._register(this._windowsShellHelper.onShellNameChanged(e => this._onProcessTitleChanged.fire(e)));
+ this._register(this._windowsShellHelper.onShellTypeChanged(e => this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: e })));
+ this._register(this._windowsShellHelper.onShellNameChanged(e => this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: e })));
});
}
// Enable the cwd detection capability if the process supports it
@@ -253,7 +249,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
const ptyProcess = (await import('node-pty')).spawn(shellLaunchConfig.executable!, args, options);
this._ptyProcess = ptyProcess;
this._childProcessMonitor = this._register(new ChildProcessMonitor(ptyProcess.pid, this._logService));
- this._childProcessMonitor.onDidChangeHasChildProcesses(this._onDidChangeHasChildProcesses.fire, this._onDidChangeHasChildProcesses);
+ this._childProcessMonitor.onDidChangeHasChildProcesses(value => this._onDidChangeProperty.fire({ type: ProcessPropertyType.HasChildProcesses, value }));
this._processStartupComplete = new Promise(c => {
this.onProcessReady(() => c());
});
@@ -360,7 +356,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
return;
}
this._currentTitle = ptyProcess.process;
- this._onProcessTitleChanged.fire(this._currentTitle);
+ this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: this._currentTitle });
}
shutdown(immediate: boolean): void {
@@ -402,21 +398,31 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
async refreshProperty(type: ProcessPropertyType): Promise {
- if (type === ProcessPropertyType.Cwd) {
- const newCwd = await this.getCwd();
- if (newCwd !== this._properties.cwd) {
- this._properties.cwd = newCwd;
- this._onDidChangeProperty.fire({ type: ProcessPropertyType.Cwd, value: this._properties.cwd });
- }
- return newCwd;
- } else {
- return this.getInitialCwd();
+ switch (type) {
+ case ProcessPropertyType.Cwd:
+ const newCwd = await this.getCwd();
+ if (newCwd !== this._properties.cwd) {
+ this._properties.cwd = newCwd;
+ this._onDidChangeProperty.fire({ type: ProcessPropertyType.Cwd, value: this._properties.cwd });
+ }
+ return newCwd as IProcessPropertyMap[T];
+ case ProcessPropertyType.InitialCwd:
+ const initialCwd = await this.getInitialCwd();
+ if (initialCwd !== this._properties.initialCwd) {
+ this._properties.initialCwd = initialCwd;
+ this._onDidChangeProperty.fire({ type: ProcessPropertyType.InitialCwd, value: this._properties.initialCwd });
+ }
+ return initialCwd as IProcessPropertyMap[T];
+ case ProcessPropertyType.Title:
+ return this.currentTitle as IProcessPropertyMap[T];
+ default:
+ return this.shellType as IProcessPropertyMap[T];
}
}
async updateProperty(type: ProcessPropertyType, value: IProcessPropertyMap[T]): Promise {
//TODO: why is the type check necessary?
- if (type === ProcessPropertyType.FixedDimensions && typeof value !== 'string') {
+ if (type === ProcessPropertyType.FixedDimensions && typeof value !== 'string' && value && ('cols' in value || 'rows' in value)) {
this._properties.fixedDimensions = value;
}
}
diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts
index 2da7bec19bb..5a19922b283 100644
--- a/src/vs/platform/theme/common/colorRegistry.ts
+++ b/src/vs/platform/theme/common/colorRegistry.ts
@@ -25,6 +25,16 @@ export interface ColorContribution {
readonly deprecationMessage: string | undefined;
}
+/**
+ * Returns the css variable name for the given color identifier. Dots (`.`) are replaced with hyphens (`-`) and
+ * everything is prefixed with `--vscode-`.
+ *
+ * @sample `editorSuggestWidget.background` is `--vscode-editorSuggestWidget-background`.
+ */
+export function asCssVariableName(colorIdent: ColorIdentifier): string {
+ return `--vscode-${colorIdent.replace('.', '-')}`;
+}
+
export const enum ColorTransformType {
Darken,
Lighten,
diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts
index 88a72a635a9..3806e892161 100644
--- a/src/vs/platform/windows/electron-main/windowsMainService.ts
+++ b/src/vs/platform/windows/electron-main/windowsMainService.ts
@@ -19,8 +19,7 @@ import { basename, join, normalize, posix } from 'vs/base/common/path';
import { getMarks, mark } from 'vs/base/common/performance';
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
import { cwd } from 'vs/base/common/process';
-import { extUriBiasedIgnorePathCase, normalizePath, originalFSPath, removeTrailingPathSeparator } from 'vs/base/common/resources';
-import { equalsIgnoreCase } from 'vs/base/common/strings';
+import { extUriBiasedIgnorePathCase, isEqualAuthority, normalizePath, originalFSPath, removeTrailingPathSeparator } from 'vs/base/common/resources';
import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
@@ -1198,7 +1197,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return false;
}
- return getRemoteAuthority(uri) === remoteAuthority;
+ return isEqualAuthority(getRemoteAuthority(uri), remoteAuthority);
});
folderUris = folderUris.filter(folderUriStr => {
@@ -1207,7 +1206,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return false;
}
- return folderUri ? getRemoteAuthority(folderUri) === remoteAuthority : false;
+ return folderUri ? isEqualAuthority(getRemoteAuthority(folderUri), remoteAuthority) : false;
});
fileUris = fileUris.filter(fileUriStr => {
@@ -1216,7 +1215,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return false;
}
- return fileUri ? getRemoteAuthority(fileUri) === remoteAuthority : false;
+ return fileUri ? isEqualAuthority(getRemoteAuthority(fileUri), remoteAuthority) : false;
});
openConfig.cli._ = cliArgs;
@@ -1477,7 +1476,3 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return this.getWindowById(browserWindow.id);
}
}
-
-function isEqualAuthority(a1: string | undefined, a2: string | undefined) {
- return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));
-}
diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts
index 4cb714335cd..9c7a5c2785f 100644
--- a/src/vs/platform/workspaces/common/workspaces.ts
+++ b/src/vs/platform/workspaces/common/workspaces.ts
@@ -12,7 +12,7 @@ import { normalizeDriveLetter } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
import { extname, isAbsolute } from 'vs/base/common/path';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
-import { extname as resourceExtname, extUriBiasedIgnorePathCase, IExtUri } from 'vs/base/common/resources';
+import { extname as resourceExtname, extUriBiasedIgnorePathCase, IExtUri, isEqualAuthority } from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -398,7 +398,7 @@ export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string,
const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], rewrittenFolders, formattingOptions);
let newContent = jsonEdit.applyEdits(rawWorkspaceContents, edits);
- if (storedWorkspace.remoteAuthority === getRemoteAuthority(targetConfigPathURI)) {
+ if (isEqualAuthority(storedWorkspace.remoteAuthority, getRemoteAuthority(targetConfigPathURI))) {
// unsaved remote workspaces have the remoteAuthority set. Remove it when no longer nexessary.
newContent = jsonEdit.applyEdits(newContent, jsonEdit.removeProperty(newContent, ['remoteAuthority'], formattingOptions));
}
@@ -547,6 +547,7 @@ export function toStoreData(recents: IRecentlyOpened): RecentlyOpenedStorageData
for (const recent of recents.files) {
serialized.entries.push({ fileUri: recent.fileUri.toString(), label: recent.label, remoteAuthority: recent.remoteAuthority });
}
+
return serialized;
}
diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts
index 63ecd2e5312..c5f4f948a43 100644
--- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts
+++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts
@@ -41,11 +41,13 @@ export interface IWorkspacesHistoryMainService {
export class WorkspacesHistoryMainService extends Disposable implements IWorkspacesHistoryMainService {
- private static readonly MAX_TOTAL_RECENT_ENTRIES = 100;
+ private static readonly MAX_TOTAL_RECENT_ENTRIES = 500;
private static readonly MAX_MACOS_DOCK_RECENT_WORKSPACES = 7; // prefer higher number of workspaces...
private static readonly MAX_MACOS_DOCK_RECENT_ENTRIES_TOTAL = 10; // ...over number of files
+ private static readonly MAX_WINDOWS_JUMP_LIST_ENTRIES = 7;
+
// Exclude some very common files from the dock/taskbar
private static readonly COMMON_FILES_FILTER = [
'COMMIT_EDITMSG',
@@ -98,21 +100,21 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
// Workspace
if (isRecentWorkspace(recent)) {
- if (!this.workspacesManagementMainService.isUntitledWorkspace(recent.workspace) && indexOfWorkspace(workspaces, recent.workspace) === -1) {
+ if (!this.workspacesManagementMainService.isUntitledWorkspace(recent.workspace) && this.indexOfWorkspace(workspaces, recent.workspace) === -1) {
workspaces.push(recent);
}
}
// Folder
else if (isRecentFolder(recent)) {
- if (indexOfFolder(workspaces, recent.folderUri) === -1) {
+ if (this.indexOfFolder(workspaces, recent.folderUri) === -1) {
workspaces.push(recent);
}
}
// File
else {
- const alreadyExistsInHistory = indexOfFile(files, recent.fileUri) >= 0;
+ const alreadyExistsInHistory = this.indexOfFile(files, recent.fileUri) >= 0;
const shouldBeFiltered = recent.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(recent.fileUri)) >= 0;
if (!alreadyExistsInHistory && !shouldBeFiltered) {
@@ -147,7 +149,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
removeRecentlyOpened(recentToRemove: URI[]): void {
const keep = (recent: IRecent) => {
- const uri = location(recent);
+ const uri = this.location(recent);
for (const resourceToRemove of recentToRemove) {
if (extUriBiasedIgnorePathCase.isEqual(resourceToRemove, uri)) {
return false;
@@ -187,7 +189,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
const workspaceEntries: string[] = [];
let entries = 0;
for (let i = 0; i < mru.workspaces.length && entries < WorkspacesHistoryMainService.MAX_MACOS_DOCK_RECENT_WORKSPACES; i++) {
- const loc = location(mru.workspaces[i]);
+ const loc = this.location(mru.workspaces[i]);
if (loc.scheme === Schemas.file) {
const workspacePath = originalFSPath(loc);
if (await Promises.exists(workspacePath)) {
@@ -200,7 +202,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
// Collect max-N recent files that are known to exist
const fileEntries: string[] = [];
for (let i = 0; i < mru.files.length && entries < WorkspacesHistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES_TOTAL; i++) {
- const loc = location(mru.files[i]);
+ const loc = this.location(mru.files[i]);
if (loc.scheme === Schemas.file) {
const filePath = originalFSPath(loc);
if (
@@ -256,7 +258,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
if (currentFiles) {
for (let currentFile of currentFiles) {
const fileUri = currentFile.fileUri;
- if (fileUri && indexOfFile(files, fileUri) === -1) {
+ if (fileUri && this.indexOfFile(files, fileUri) === -1) {
files.push({ fileUri });
}
}
@@ -272,7 +274,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
// Get from storage
let recents = this.getRecentlyOpenedFromStorage();
for (let recent of recents.workspaces) {
- let index = isRecentFolder(recent) ? indexOfFolder(workspaces, recent.folderUri) : indexOfWorkspace(workspaces, recent.workspace);
+ let index = isRecentFolder(recent) ? this.indexOfFolder(workspaces, recent.folderUri) : this.indexOfWorkspace(workspaces, recent.workspace);
if (index >= 0) {
workspaces[index].label = workspaces[index].label || recent.label;
} else {
@@ -281,7 +283,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
}
for (let recent of recents.files) {
- let index = indexOfFile(files, recent.fileUri);
+ let index = this.indexOfFile(files, recent.fileUri);
if (index >= 0) {
files[index].label = files[index].label || recent.label;
} else {
@@ -346,7 +348,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
// Add entries
let hasWorkspaces = false;
- const items: JumpListItem[] = coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => {
+ const items: JumpListItem[] = coalesce(this.getRecentlyOpened().workspaces.slice(0, WorkspacesHistoryMainService.MAX_WINDOWS_JUMP_LIST_ENTRIES).map(recent => {
const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri;
const { title, description } = this.getWindowsJumpListLabel(workspace, recent.label);
@@ -399,7 +401,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
// Single Folder
if (URI.isUri(workspace)) {
- return { title: basename(workspace), description: renderJumpListPathDescription(workspace) };
+ return { title: basename(workspace), description: this.renderJumpListPathDescription(workspace) };
}
// Workspace: Untitled
@@ -413,34 +415,34 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
- return { title: localize('workspaceName', "{0} (Workspace)", filename), description: renderJumpListPathDescription(workspace.configPath) };
- }
-}
-
-function renderJumpListPathDescription(uri: URI) {
- return uri.scheme === 'file' ? normalizeDriveLetter(uri.fsPath) : uri.toString();
-}
-
-function location(recent: IRecent): URI {
- if (isRecentFolder(recent)) {
- return recent.folderUri;
+ return { title: localize('workspaceName', "{0} (Workspace)", filename), description: this.renderJumpListPathDescription(workspace.configPath) };
}
- if (isRecentFile(recent)) {
- return recent.fileUri;
+ private renderJumpListPathDescription(uri: URI) {
+ return uri.scheme === 'file' ? normalizeDriveLetter(uri.fsPath) : uri.toString();
}
- return recent.workspace.configPath;
-}
+ private location(recent: IRecent): URI {
+ if (isRecentFolder(recent)) {
+ return recent.folderUri;
+ }
-function indexOfWorkspace(arr: IRecent[], candidate: IWorkspaceIdentifier): number {
- return arr.findIndex(workspace => isRecentWorkspace(workspace) && workspace.workspace.id === candidate.id);
-}
+ if (isRecentFile(recent)) {
+ return recent.fileUri;
+ }
-function indexOfFolder(arr: IRecent[], candidate: URI): number {
- return arr.findIndex(folder => isRecentFolder(folder) && extUriBiasedIgnorePathCase.isEqual(folder.folderUri, candidate));
-}
+ return recent.workspace.configPath;
+ }
-function indexOfFile(arr: IRecentFile[], candidate: URI): number {
- return arr.findIndex(file => extUriBiasedIgnorePathCase.isEqual(file.fileUri, candidate));
+ private indexOfWorkspace(arr: IRecent[], candidate: IWorkspaceIdentifier): number {
+ return arr.findIndex(workspace => isRecentWorkspace(workspace) && workspace.workspace.id === candidate.id);
+ }
+
+ private indexOfFolder(arr: IRecent[], candidate: URI): number {
+ return arr.findIndex(folder => isRecentFolder(folder) && extUriBiasedIgnorePathCase.isEqual(folder.folderUri, candidate));
+ }
+
+ private indexOfFile(arr: IRecentFile[], candidate: URI): number {
+ return arr.findIndex(file => extUriBiasedIgnorePathCase.isEqual(file.fileUri, candidate));
+ }
}
diff --git a/src/vs/server/remoteCli.ts b/src/vs/server/remoteCli.ts
index 0cdcf016205..fcc82d710ae 100644
--- a/src/vs/server/remoteCli.ts
+++ b/src/vs/server/remoteCli.ts
@@ -16,6 +16,15 @@ import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
import { PipeCommand } from 'vs/workbench/api/node/extHostCLIServer';
import { hasStdinWithoutTty, getStdinFilePath, readFromStdin } from 'vs/platform/environment/node/stdin';
+/*
+ * Implements a standalone CLI app that opens VS Code from a remote terminal.
+ * - In integrated terminals for remote windows this connects to the remote server though a pipe.
+ * The pipe is passed in env VSCODE_IPC_HOOK_CLI.
+ * - In external terminals for WSL this calls VS Code on the Windows side.
+ * The VS Code desktop executable path is passed in env VSCODE_CLIENT_COMMAND.
+ */
+
+
interface ProductDescription {
productName: string;
version: string;
diff --git a/src/vs/server/remoteTerminalChannel.ts b/src/vs/server/remoteTerminalChannel.ts
index dec98015031..2c31f0cd99e 100644
--- a/src/vs/server/remoteTerminalChannel.ts
+++ b/src/vs/server/remoteTerminalChannel.ts
@@ -128,6 +128,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
case '$setTerminalLayoutInfo': return this._setTerminalLayoutInfo(args);
case '$serializeTerminalState': return this._ptyService.serializeTerminalState.apply(this._ptyService, args);
case '$reviveTerminalProcesses': return this._ptyService.reviveTerminalProcesses.apply(this._ptyService, args);
+ case '$setUnicodeVersion': return this._ptyService.setUnicodeVersion.apply(this._ptyService, args);
case '$reduceConnectionGraceTime': return this._reduceConnectionGraceTime();
case '$updateIcon': return this._ptyService.updateIcon.apply(this._ptyService, args);
case '$updateTitle': return this._ptyService.updateTitle.apply(this._ptyService, args);
@@ -148,15 +149,10 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
case '$onPtyHostResponsiveEvent': return this._ptyService.onPtyHostResponsive || Event.None;
case '$onPtyHostRequestResolveVariablesEvent': return this._ptyService.onPtyHostRequestResolveVariables || Event.None;
case '$onProcessDataEvent': return this._ptyService.onProcessData;
- case '$onProcessExitEvent': return this._ptyService.onProcessExit;
case '$onProcessReadyEvent': return this._ptyService.onProcessReady;
+ case '$onProcessExitEvent': return this._ptyService.onProcessExit;
case '$onProcessReplayEvent': return this._ptyService.onProcessReplay;
- case '$onProcessTitleChangedEvent': return this._ptyService.onProcessTitleChanged;
- case '$onProcessShellTypeChangedEvent': return this._ptyService.onProcessShellTypeChanged;
- case '$onProcessOverrideDimensionsEvent': return this._ptyService.onProcessOverrideDimensions;
- case '$onProcessResolvedShellLaunchConfigEvent': return this._ptyService.onProcessResolvedShellLaunchConfig;
case '$onProcessOrphanQuestion': return this._ptyService.onProcessOrphanQuestion;
- case '$onProcessDidChangeHasChildProcesses': return this._ptyService.onProcessDidChangeHasChildProcesses;
case '$onExecuteCommand': return this.onExecuteCommand;
case '$onDidRequestDetach': return this._ptyService.onDidRequestDetach || Event.None;
case '$onDidChangeProperty': return this._ptyService.onDidChangeProperty;
diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts
index 39129a134cf..b4af49f2c96 100644
--- a/src/vs/vscode.proposed.d.ts
+++ b/src/vs/vscode.proposed.d.ts
@@ -1826,6 +1826,7 @@ declare module 'vscode' {
/**
* The text of the hint.
*/
+ // todo@API label?
text: string;
/**
* The position of this hint.
diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts
index cc7db0622e0..e213baa9dfb 100644
--- a/src/vs/workbench/api/browser/mainThreadComments.ts
+++ b/src/vs/workbench/api/browser/mainThreadComments.ts
@@ -138,7 +138,7 @@ export class MainThreadCommentThread implements modes.CommentThread {
if (modified('range')) { this._range = changes.range!; }
if (modified('label')) { this._label = changes.label; }
- if (modified('contextValue')) { this._contextValue = changes.contextValue; }
+ if (modified('contextValue')) { this._contextValue = changes.contextValue === null ? undefined : changes.contextValue; }
if (modified('comments')) { this._comments = changes.comments; }
if (modified('collapseState')) { this._collapsibleState = changes.collapseState; }
if (modified('canReply')) { this.canReply = changes.canReply!; }
diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts
index 68c5acf3c18..4e8435e598e 100644
--- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts
+++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts
@@ -10,13 +10,13 @@ import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
-import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions, TerminalLocation, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { IProcessProperty, IShellLaunchConfig, IShellLaunchConfigDto, ProcessPropertyType, TerminalLocation, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
import { ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalLink, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
-import { IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
+import { IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { OperatingSystem, OS } from 'vs/base/common/platform';
@@ -57,7 +57,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
- @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService
+ @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService,
+ @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService
) {
this._proxy = _extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
@@ -97,7 +98,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._os = env?.os || OS;
this._updateDefaultProfile();
});
- this._terminalService.onDidChangeAvailableProfiles(() => this._updateDefaultProfile());
+ this._terminalProfileService.onDidChangeAvailableProfiles(() => this._updateDefaultProfile());
}
public dispose(): void {
@@ -141,8 +142,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal,
useShellEnvironment: launchConfig.useShellEnvironment,
};
- // eslint-disable-next-line no-async-promise-executor
-
const terminal = Promises.withAsyncBody(async r => {
const terminal = await this._terminalService.createTerminal({
config: shellLaunchConfig,
@@ -191,6 +190,10 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
await instance?.sendText(text, addNewLine);
}
+ public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
+ this._terminalProcessProxies.get(terminalId)?.emitExit(exitCode);
+ }
+
public $startSendingDataEvents(): void {
if (!this._dataEventTracker) {
this._dataEventTracker = this._instantiationService.createInstance(TerminalDataEventTracker, (id, data) => {
@@ -224,7 +227,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
public $registerProfileProvider(id: string, extensionIdentifier: string): void {
// Proxy profile provider requests through the extension host
- this._profileProviders.set(id, this._terminalService.registerTerminalProfileProvider(extensionIdentifier, id, {
+ this._profileProviders.set(id, this._terminalProfileService.registerTerminalProfileProvider(extensionIdentifier, id, {
createContributedTerminalProfile: async (options) => {
return this._proxy.$createContributedProfileTerminal(id, options);
}
@@ -303,18 +306,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
proxy.onRequestLatency(() => this._onRequestLatency(proxy.instanceId));
}
- public $sendProcessTitle(terminalId: number, title: string): void {
- // Since title events can only come from vscode.Pseudoterminals right now, these are routed
- // directly to the instance as API source events such that they will replace the initial
- // `name` property provided for the Pseudoterminal. If we support showing both Api and
- // Process titles at the same time we may want to pass this through as a Process source
- // event.
- const instance = this._terminalService.getInstanceFromId(terminalId);
- if (instance) {
- instance.refreshTabLabels(title, TitleEventSource.Api);
- }
- }
-
public $sendProcessData(terminalId: number, data: string): void {
this._terminalProcessProxies.get(terminalId)?.emitData(data);
}
@@ -323,24 +314,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._terminalProcessProxies.get(terminalId)?.emitReady(pid, cwd);
}
- public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
- this._terminalProcessProxies.get(terminalId)?.emitExit(exitCode);
- }
-
- public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void {
- this._terminalProcessProxies.get(terminalId)?.emitOverrideDimensions(dimensions);
- }
-
- public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
- this._terminalProcessProxies.get(terminalId)?.emitInitialCwd(initialCwd);
- }
-
- public $sendProcessCwd(terminalId: number, cwd: string): void {
- this._terminalProcessProxies.get(terminalId)?.emitCwd(cwd);
- }
-
- public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void {
- this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig);
+ public $sendProcessProperty(terminalId: number, property: IProcessProperty): void {
+ if (property.type === ProcessPropertyType.Title) {
+ const instance = this._terminalService.getInstanceFromId(terminalId);
+ if (instance) {
+ instance.refreshTabLabels(property.value, TitleEventSource.Api);
+ }
+ }
+ this._terminalProcessProxies.get(terminalId)?.emitProcessProperty(property);
}
private async _onRequestLatency(terminalId: number): Promise {
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 75cfaf00eb2..6779800a4b7 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -39,7 +39,7 @@ import { IRemoteConnectionData, RemoteAuthorityResolverErrorCode, ResolverResult
import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
-import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal';
+import { ICreateContributedTerminalProfileOptions, IProcessProperty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust';
@@ -154,7 +154,7 @@ export interface CommentProviderFeatures {
export type CommentThreadChanges = Partial<{
range: IRange,
label: string,
- contextValue: string,
+ contextValue: string | null,
comments: modes.Comment[],
collapseState: modes.CommentThreadCollapsibleState;
canReply: boolean;
@@ -505,14 +505,10 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void;
// Process
- $sendProcessTitle(terminalId: number, title: string): void;
$sendProcessData(terminalId: number, data: string): void;
$sendProcessReady(terminalId: number, pid: number, cwd: string): void;
+ $sendProcessProperty(terminalId: number, property: IProcessProperty): void;
$sendProcessExit(terminalId: number, exitCode: number | undefined): void;
- $sendProcessInitialCwd(terminalId: number, cwd: string): void;
- $sendProcessCwd(terminalId: number, initialCwd: string): void;
- $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void;
- $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void;
}
export interface TransferQuickPickItems extends quickInput.IQuickPickItem {
diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts
index f15578fb0ae..ab15113e655 100644
--- a/src/vs/workbench/api/common/extHostComments.ts
+++ b/src/vs/workbench/api/common/extHostComments.ts
@@ -425,7 +425,11 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
formattedModifications.label = this.label;
}
if (modified('contextValue')) {
- formattedModifications.contextValue = this.contextValue;
+ /*
+ * null -> cleared contextValue
+ * undefined -> no change
+ */
+ formattedModifications.contextValue = this.contextValue ?? null;
}
if (modified('comments')) {
formattedModifications.comments =
diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts
index bf2bbb30e97..aa0d9c70151 100644
--- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts
+++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts
@@ -42,8 +42,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
this._disposables.add(extHostDocuments.onDidChangeDocument(e => {
const cellIdx = this._cellUris.get(e.document.uri);
if (cellIdx !== undefined) {
- this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1);
- this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount);
+ this._cellLengths.setValue(cellIdx, this._cells[cellIdx].document.getText().length + 1);
+ this._cellLines.setValue(cellIdx, this._cells[cellIdx].document.lineCount);
this._versionId += 1;
this._onDidChange.fire(undefined);
}
diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts
index 13339a21ba2..baceec91f5c 100644
--- a/src/vs/workbench/api/common/extHostTask.ts
+++ b/src/vs/workbench/api/common/extHostTask.ts
@@ -492,10 +492,6 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number, resolvedDefinition: tasks.TaskDefinitionDTO): Promise {
const customExecution: types.CustomExecution | undefined = this._providedCustomExecutions2.get(execution.id);
if (customExecution) {
- if (this._activeCustomExecutions2.get(execution.id) !== undefined) {
- throw new Error('We should not be trying to start the same custom task executions twice.');
- }
-
// Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task.
this._activeCustomExecutions2.set(execution.id, customExecution);
this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback(resolvedDefinition));
@@ -625,6 +621,8 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
const taskId = await this._proxy.$createTaskId(taskDTO);
if (!isProvided && !this._providedCustomExecutions2.has(taskId)) {
this._notProvidedCustomExecutions.add(taskId);
+ // Also add to active executions when not coming from a provider to prevent timing issue.
+ this._activeCustomExecutions2.set(taskId, task.execution);
}
this._providedCustomExecutions2.set(taskId, task.execution);
}
@@ -642,13 +640,20 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
if (result) {
return result;
}
- // eslint-disable-next-line no-async-promise-executor
- const createdResult: Promise = new Promise(async (resolve, reject) => {
- const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider, this._providedCustomExecutions2);
- if (!taskToCreate) {
- reject('Unexpected: Task does not exist.');
+ const createdResult: Promise = new Promise((resolve, reject) => {
+ function resolvePromiseWithCreatedTask(that: ExtHostTaskBase, execution: tasks.TaskExecutionDTO, taskToCreate: vscode.Task | types.Task | undefined) {
+ if (!taskToCreate) {
+ reject('Unexpected: Task does not exist.');
+ } else {
+ resolve(new TaskExecutionImpl(that, execution.id, taskToCreate));
+ }
+ }
+
+ if (task) {
+ resolvePromiseWithCreatedTask(this, execution, task);
} else {
- resolve(new TaskExecutionImpl(this, execution.id, taskToCreate));
+ TaskDTO.to(execution.task, this._workspaceProvider, this._providedCustomExecutions2)
+ .then(task => resolvePromiseWithCreatedTask(this, execution, task));
}
});
diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts
index 0309ac1ff3b..af9e4acda97 100644
--- a/src/vs/workbench/api/common/extHostTerminalService.ts
+++ b/src/vs/workbench/api/common/extHostTerminalService.ts
@@ -18,7 +18,7 @@ import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/ter
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { generateUuid } from 'vs/base/common/uuid';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
-import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, TerminalShellType, IShellLaunchConfig, ProcessPropertyType, ProcessCapability, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal';
+import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, ProcessCapability, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { withNullAsUndefined } from 'vs/base/common/types';
@@ -249,30 +249,21 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
get capabilities(): ProcessCapability[] { return this._capabilities; }
private readonly _onProcessData = new Emitter();
public readonly onProcessData: Event = this._onProcessData.event;
- private readonly _onProcessExit = new Emitter();
- public readonly onProcessExit: Event = this._onProcessExit.event;
private readonly _onProcessReady = new Emitter();
public get onProcessReady(): Event { return this._onProcessReady.event; }
- private readonly _onProcessTitleChanged = new Emitter();
- public readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event;
- private readonly _onProcessOverrideDimensions = new Emitter();
- public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; }
- private readonly _onProcessShellTypeChanged = new Emitter();
- public readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
private readonly _onDidChangeProperty = new Emitter>();
public readonly onDidChangeProperty = this._onDidChangeProperty.event;
-
+ private readonly _onProcessExit = new Emitter();
+ public readonly onProcessExit: Event = this._onProcessExit.event;
constructor(private readonly _pty: vscode.Pseudoterminal) { }
- onProcessResolvedShellLaunchConfig?: Event | undefined;
- onDidChangeHasChildProcesses?: Event | undefined;
refreshProperty(property: ProcessPropertyType): Promise {
- return Promise.resolve('');
+ throw new Error(`refreshProperty is not suppported in extension owned terminals. property: ${property}`);
}
- async updateProperty(property: ProcessPropertyType, value: IProcessPropertyMap[T]): Promise {
- Promise.resolve('');
+ updateProperty(property: ProcessPropertyType, value: IProcessPropertyMap[T]): Promise {
+ throw new Error(`updateProperty is not suppported in extension owned terminals. property: ${property}, value: ${value}`);
}
async start(): Promise {
@@ -329,10 +320,16 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
});
}
if (this._pty.onDidOverrideDimensions) {
- this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : e));
+ this._pty.onDidOverrideDimensions(e => {
+ if (e) {
+ this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: { cols: e.columns, rows: e.rows } });
+ }
+ });
}
if (this._pty.onDidChangeName) {
- this._pty.onDidChangeName(title => this._onProcessTitleChanged.fire(title));
+ this._pty.onDidChangeName(title => {
+ this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: title });
+ });
}
this._pty.open(initialDimensions ? initialDimensions : undefined);
@@ -589,17 +586,12 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): IDisposable {
const disposables = new DisposableStore();
-
disposables.add(p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd)));
- disposables.add(p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)));
+ disposables.add(p.onDidChangeProperty(property => this._proxy.$sendProcessProperty(id, property)));
// Buffer data events to reduce the amount of messages going to the renderer
this._bufferer.startBuffering(id, p.onProcessData);
disposables.add(p.onProcessExit(exitCode => this._onProcessExit(id, exitCode)));
-
- if (p.onProcessOverrideDimensions) {
- disposables.add(p.onProcessOverrideDimensions(e => this._proxy.$sendOverrideDimensions(id, e)));
- }
this._terminalProcesses.set(id, p);
const awaitingStart = this._extensionTerminalAwaitingStart[id];
@@ -642,11 +634,11 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
}
public $acceptProcessRequestInitialCwd(id: number): void {
- this._terminalProcesses.get(id)?.getInitialCwd().then(initialCwd => this._proxy.$sendProcessInitialCwd(id, initialCwd));
+ this._terminalProcesses.get(id)?.getInitialCwd().then(initialCwd => this._proxy.$sendProcessProperty(id, { type: ProcessPropertyType.InitialCwd, value: initialCwd }));
}
public $acceptProcessRequestCwd(id: number): void {
- this._terminalProcesses.get(id)?.getCwd().then(cwd => this._proxy.$sendProcessCwd(id, cwd));
+ this._terminalProcesses.get(id)?.getCwd().then(cwd => this._proxy.$sendProcessProperty(id, { type: ProcessPropertyType.Cwd, value: cwd }));
}
public $acceptProcessRequestLatency(id: number): number {
@@ -777,7 +769,6 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
processDiposable.dispose();
delete this._terminalProcessDisposables[id];
}
-
// Send exit event to main side
this._proxy.$sendProcessExit(id, exitCode);
}
diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts
index 0d032154fe0..6361a12975b 100644
--- a/src/vs/workbench/browser/actions/developerActions.ts
+++ b/src/vs/workbench/browser/actions/developerActions.ts
@@ -19,7 +19,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { timeout } from 'vs/base/common/async';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { Registry } from 'vs/platform/registry/common/platform';
-import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
+import { registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { clamp } from 'vs/base/common/numbers';
import { KeyCode } from 'vs/base/common/keyCodes';
@@ -217,11 +217,37 @@ class ToggleScreencastModeAction extends Action2 {
length = 0;
}
+ const format = configurationService.getValue<'keys' | 'command' | 'commandWithGroup' | 'commandAndKeys' | 'commandWithGroupAndKeys'>('screencastMode.keyboardShortcutsFormat');
const keybinding = keybindingService.resolveKeyboardEvent(event);
- const label = keybinding.getLabel();
- const key = $('span.key', {}, label || '');
+ const command = shortcut?.commandId ? MenuRegistry.getCommand(shortcut.commandId) : null;
+
+ let titleLabel = '';
+ let keyLabel = keybinding.getLabel();
+
+ if (command) {
+ titleLabel = typeof command.title === 'string' ? command.title : command.title.value;
+
+ if ((format === 'commandWithGroup' || format === 'commandWithGroupAndKeys') && command.category) {
+ titleLabel = `${typeof command.category === 'string' ? command.category : command.category.value}: ${titleLabel} `;
+ }
+
+ if (shortcut?.commandId) {
+ const fullKeyLabel = keybindingService.lookupKeybinding(shortcut.commandId);
+ if (fullKeyLabel) {
+ keyLabel = fullKeyLabel.getLabel();
+ }
+ }
+ }
+
+ if (format !== 'keys' && titleLabel) {
+ append(keyboardMarker, $('span.title', {}, `${titleLabel} `));
+ }
+
+ if (format === 'keys' || format === 'commandAndKeys' || format === 'commandWithGroupAndKeys') {
+ append(keyboardMarker, $('span.key', {}, keyLabel || ''));
+ }
+
length++;
- append(keyboardMarker, key);
}
const promise = timeout(keyboardMarkerTimeout);
@@ -318,6 +344,18 @@ configurationRegistry.registerConfiguration({
maximum: 100,
description: localize('screencastMode.fontSize', "Controls the font size (in pixels) of the screencast mode keyboard.")
},
+ 'screencastMode.keyboardShortcutsFormat': {
+ enum: ['keys', 'command', 'commandWithGroup', 'commandAndKeys', 'commandWithGroupAndKeys'],
+ enumDescriptions: [
+ localize('keyboardShortcutsFormat.keys', "Keys."),
+ localize('keyboardShortcutsFormat.command', "Command title."),
+ localize('keyboardShortcutsFormat.commandWithGroup', "Command title prefixed by its group."),
+ localize('keyboardShortcutsFormat.commandAndKeys', "Command title and keys."),
+ localize('keyboardShortcutsFormat.commandWithGroupAndKeys', "Command title and keys, with the command prefixed by its group.")
+ ],
+ description: localize('screencastMode.keyboardShortcutsFormat', "Controls what is displayed in the keyboard overlay when showing only shortcuts."),
+ default: 'commandAndKeys'
+ },
'screencastMode.onlyKeyboardShortcuts': {
type: 'boolean',
description: localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in screencast mode."),
diff --git a/src/vs/workbench/browser/actions/media/actions.css b/src/vs/workbench/browser/actions/media/actions.css
index 54aeeae495a..534056c0753 100644
--- a/src/vs/workbench/browser/actions/media/actions.css
+++ b/src/vs/workbench/browser/actions/media/actions.css
@@ -46,3 +46,7 @@
border-radius: 5px;
background-color: rgba(255, 255, 255, 0.05);
}
+
+.monaco-workbench .screencast-keyboard > .title {
+ font-weight: 600;
+}
diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts
index e52386560c6..d5623298ef1 100644
--- a/src/vs/workbench/browser/layout.ts
+++ b/src/vs/workbench/browser/layout.ts
@@ -1136,6 +1136,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.state.zenMode.wasAuxiliaryBarPartVisible = this.isVisible(Parts.AUXILIARYBAR_PART);
this.setPanelHidden(true, true);
+ this.setAuxiliaryBarHidden(true, true);
this.setSideBarHidden(true, true);
if (config.hideActivityBar) {
@@ -1178,6 +1179,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.setPanelHidden(false, true);
}
+ if (this.state.zenMode.wasAuxiliaryBarPartVisible) {
+ this.setAuxiliaryBarHidden(false, true);
+ }
+
if (this.state.zenMode.wasSideBarVisible) {
this.setSideBarHidden(false, true);
}
@@ -1684,7 +1689,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
private setAuxiliaryBarHidden(hidden: boolean, skipLayout?: boolean): void {
- if (!this.configurationService.getValue(Settings.AUXILIARYBAR_ENABLED)) {
+ if (!this.configurationService || !this.configurationService.getValue(Settings.AUXILIARYBAR_ENABLED)) {
return;
}
diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts
index 137e6bd4023..aa3b4855816 100644
--- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts
+++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts
@@ -95,7 +95,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
return; // no auto save for readonly or untitled editors
}
- // Determine if we need to save all. In case of a window focus change we also save if
+ // Determine if we need to save all. In case of a window focus change we also save if
// auto save mode is configured to be ON_FOCUS_CHANGE (editor focus change)
const mode = this.filesConfigurationService.getAutoSaveMode();
if (
@@ -115,7 +115,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
private onAutoSaveConfigurationChange(config: IAutoSaveConfiguration, fromEvent: boolean): void {
// Update auto save after delay config
- this.autoSaveAfterDelay = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay > 0 ? config.autoSaveDelay : undefined;
+ this.autoSaveAfterDelay = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay >= 0 ? config.autoSaveDelay : undefined;
// Trigger a save-all when auto save is enabled
if (fromEvent) {
diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
index 53179f98fb8..b446ee591ad 100644
--- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts
+++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
@@ -1059,26 +1059,31 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
let openEditorPromise: Promise;
if (context.active) {
openEditorPromise = (async () => {
- const result = await this.editorPane.openEditor(editor, options, { newInGroup: context.isNew });
+ const { pane, changed, cancelled, error } = await this.editorPane.openEditor(editor, options, { newInGroup: context.isNew });
+
+ // Return early if the operation was cancelled by another operation
+ if (cancelled) {
+ return undefined;
+ }
// Editor change event
- if (result.editorChanged) {
+ if (changed) {
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor });
}
// Handle errors but do not bubble them up
- if (result.error) {
- await this.doHandleOpenEditorError(result.error, editor, options);
+ if (error) {
+ await this.doHandleOpenEditorError(error, editor, options);
}
// Without an editor pane, recover by closing the active editor
// (if the input is still the active one)
- if (!result.editorPane && this.activeEditor === editor) {
+ if (!pane && this.activeEditor === editor) {
const focusNext = !options || !options.preserveFocus;
this.doCloseEditor(editor, focusNext, { fromError: true });
}
- return result.editorPane;
+ return pane;
})();
} else {
openEditorPromise = Promise.resolve(undefined); // inactive: return undefined as result to signal this
diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts
index ccb86f9477d..13e9fbc0e8d 100644
--- a/src/vs/workbench/browser/parts/editor/editorPanes.ts
+++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts
@@ -12,7 +12,7 @@ import { IEditorPaneRegistry, IEditorPaneDescriptor } from 'vs/workbench/browser
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
+import { IEditorProgressService, IOperation, LongRunningOperation } from 'vs/platform/progress/common/progress';
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { Emitter } from 'vs/base/common/event';
import { assertIsDefined } from 'vs/base/common/types';
@@ -32,12 +32,12 @@ export interface IOpenEditorResult {
* open the editor and in cases where no placeholder is being
* used.
*/
- readonly editorPane?: EditorPane;
+ readonly pane?: EditorPane;
/**
* Whether the editor changed as a result of opening.
*/
- readonly editorChanged?: boolean;
+ readonly changed?: boolean;
/**
* This property is set when an editor fails to restore and
@@ -45,6 +45,14 @@ export interface IOpenEditorResult {
* to still present the error to the user in that case.
*/
readonly error?: Error;
+
+ /**
+ * This property indicates whether the open editor operation was
+ * cancelled or not. The operation may have been cancelled
+ * in case another editor open operation was triggered right
+ * after cancelling this one out.
+ */
+ readonly cancelled?: boolean;
}
export class EditorPanes extends Disposable {
@@ -136,11 +144,32 @@ export class EditorPanes extends Disposable {
private async doOpenEditor(descriptor: IEditorPaneDescriptor, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext = Object.create(null)): Promise