diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json
index 963da6581ec174b954726d270803bbb2b879510a..daf7bac0107b5c3e89fdefaabe47d079d220ed19 100644
--- a/extensions/markdown-language-features/package.json
+++ b/extensions/markdown-language-features/package.json
@@ -23,7 +23,7 @@
"onCommand:markdown.showLockedPreviewToSide",
"onCommand:markdown.showSource",
"onCommand:markdown.showPreviewSecuritySelector",
- "onView:markdown.preview"
+ "onWebviewPanel:markdown.preview"
],
"contributes": {
"commands": [
diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts
index 5312e4109bb5aecb16b1c604f871d397c28cd801..a9d95e36ce87e3789a5cf362db95bdb6bd771541 100644
--- a/extensions/markdown-language-features/src/extension.ts
+++ b/extensions/markdown-language-features/src/extension.ts
@@ -17,6 +17,7 @@ import { MarkdownEngine } from './markdownEngine';
import { getMarkdownExtensionContributions } from './markdownExtensions';
import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security';
import { loadDefaultTelemetryReporter } from './telemetryReporter';
+import { githubSlugifier } from './slugify';
export function activate(context: vscode.ExtensionContext) {
@@ -26,7 +27,7 @@ export function activate(context: vscode.ExtensionContext) {
const contributions = getMarkdownExtensionContributions();
const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState);
- const engine = new MarkdownEngine(contributions);
+ const engine = new MarkdownEngine(contributions, githubSlugifier);
const logger = new Logger();
const selector: vscode.DocumentSelector = [
diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts
index 6d78b575dab0990c2c4207b9fbd8a03824d4140c..7522eb8b782dca8008b525c879cdb68124c458c3 100644
--- a/extensions/markdown-language-features/src/markdownEngine.ts
+++ b/extensions/markdown-language-features/src/markdownEngine.ts
@@ -7,7 +7,7 @@ import { MarkdownIt, Token } from 'markdown-it';
import * as path from 'path';
import * as vscode from 'vscode';
import { MarkdownContributions } from './markdownExtensions';
-import { stripSlugifier } from './slugify';
+import { Slugifier } from './slugify';
const FrontMatterRegex = /^---\s*[^]*?(-{3}|\.{3})\s*/;
@@ -19,7 +19,8 @@ export class MarkdownEngine {
private currentDocument?: vscode.Uri;
public constructor(
- private readonly extensionPreviewResourceProvider: MarkdownContributions
+ private readonly extensionPreviewResourceProvider: MarkdownContributions,
+ private readonly slugifier: Slugifier,
) { }
private usePlugin(factory: (md: any) => any): void {
@@ -49,7 +50,7 @@ export class MarkdownEngine {
return `
${this.md!.utils.escapeHtml(str)}
`;
}
}).use(mdnh, {
- slugify: (header: string) => stripSlugifier.fromHeading(header).value
+ slugify: (header: string) => this.slugifier.fromHeading(header).value
});
for (const plugin of this.extensionPreviewResourceProvider.markdownItPlugins) {
@@ -145,13 +146,13 @@ export class MarkdownEngine {
if (fragment) {
uri = uri.with({
- fragment: stripSlugifier.fromHeading(fragment).value
+ fragment: this.slugifier.fromHeading(fragment).value
});
}
return normalizeLink(uri.with({ scheme: 'vscode-resource' }).toString(true));
} else if (!uri.scheme && !uri.path && uri.fragment) {
return normalizeLink(uri.with({
- fragment: stripSlugifier.fromHeading(uri.fragment).value
+ fragment: this.slugifier.fromHeading(uri.fragment).value
}).toString(true));
}
} catch (e) {
diff --git a/extensions/markdown-language-features/src/slugify.ts b/extensions/markdown-language-features/src/slugify.ts
index 768a0c64d9751dd4299299c9403780cc4ebdaa28..c3e167e89e07d2c61b09f7ca23818a14b96a4d25 100644
--- a/extensions/markdown-language-features/src/slugify.ts
+++ b/extensions/markdown-language-features/src/slugify.ts
@@ -17,19 +17,16 @@ export interface Slugifier {
fromHeading(heading: string): Slug;
}
-export const stripSlugifier: Slugifier = new class implements Slugifier {
- private readonly specialChars: any = { 'à': 'a', 'ä': 'a', 'ã': 'a', 'á': 'a', 'â': 'a', 'æ': 'a', 'å': 'a', 'ë': 'e', 'è': 'e', 'é': 'e', 'ê': 'e', 'î': 'i', 'ï': 'i', 'ì': 'i', 'í': 'i', 'ò': 'o', 'ó': 'o', 'ö': 'o', 'ô': 'o', 'ø': 'o', 'ù': 'o', 'ú': 'u', 'ü': 'u', 'û': 'u', 'ñ': 'n', 'ç': 'c', 'ß': 's', 'ÿ': 'y', 'œ': 'o', 'ŕ': 'r', 'ś': 's', 'ń': 'n', 'ṕ': 'p', 'ẃ': 'w', 'ǵ': 'g', 'ǹ': 'n', 'ḿ': 'm', 'ǘ': 'u', 'ẍ': 'x', 'ź': 'z', 'ḧ': 'h', '·': '-', '/': '-', '_': '-', ',': '-', ':': '-', ';': '-', 'З': '3', 'з': '3' };
-
- public fromHeading(heading: string): Slug {
- const slugifiedHeading = encodeURI(heading.trim()
- .toLowerCase()
- .replace(/./g, c => this.specialChars[c] || c)
- .replace(/[\]\[\!\'\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`]/g, '')
- .replace(/\s+/g, '-') // Replace whitespace with -
- .replace(/[^\w\-]+/g, '') // Remove remaining non-word chars
- .replace(/^\-+/, '') // Remove leading -
- .replace(/\-+$/, '') // Remove trailing -
+export const githubSlugifier: Slugifier = new class implements Slugifier {
+ fromHeading(heading: string): Slug {
+ const slugifiedHeading = encodeURI(
+ heading.trim()
+ .toLowerCase()
+ .replace(/\s+/g, '-') // Replace whitespace with -
+ .replace(/[\]\[\!\'\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`]/g, '') // Remove known puctuators
+ .replace(/^\-+/, '') // Remove leading -
+ .replace(/\-+$/, '') // Remove trailing -
);
return new Slug(slugifiedHeading);
}
-};
\ No newline at end of file
+};
diff --git a/extensions/markdown-language-features/src/tableOfContentsProvider.ts b/extensions/markdown-language-features/src/tableOfContentsProvider.ts
index c13a52c60d2fe3708d54dd9c15f5570f14967747..30b9de8574460028fbd3af1d870a949e3f3f7e27 100644
--- a/extensions/markdown-language-features/src/tableOfContentsProvider.ts
+++ b/extensions/markdown-language-features/src/tableOfContentsProvider.ts
@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { MarkdownEngine } from './markdownEngine';
-import { Slug, stripSlugifier } from './slugify';
+import { Slug, githubSlugifier } from './slugify';
export interface TocEntry {
readonly slug: Slug;
@@ -36,7 +36,7 @@ export class TableOfContentsProvider {
public async lookup(fragment: string): Promise {
const toc = await this.getToc();
- const slug = stripSlugifier.fromHeading(fragment);
+ const slug = githubSlugifier.fromHeading(fragment);
return toc.find(entry => entry.slug.equals(slug));
}
@@ -48,7 +48,7 @@ export class TableOfContentsProvider {
const lineNumber = heading.map[0];
const line = document.lineAt(lineNumber);
toc.push({
- slug: stripSlugifier.fromHeading(line.text),
+ slug: githubSlugifier.fromHeading(line.text),
text: TableOfContentsProvider.getHeaderText(line.text),
level: TableOfContentsProvider.getHeaderLevel(heading.markup),
line: lineNumber,
diff --git a/extensions/markdown-language-features/src/test/engine.ts b/extensions/markdown-language-features/src/test/engine.ts
index a7a30a89ce06dd49259f473d2eb627e1c66a9aa7..860bafad7cc5719578f718452f2841afa1836a8e 100644
--- a/extensions/markdown-language-features/src/test/engine.ts
+++ b/extensions/markdown-language-features/src/test/engine.ts
@@ -6,13 +6,15 @@
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { MarkdownContributions } from '../markdownExtensions';
+import { githubSlugifier } from '../slugify';
+
+const emptyContributions = new class implements MarkdownContributions {
+ readonly previewScripts: vscode.Uri[] = [];
+ readonly previewStyles: vscode.Uri[] = [];
+ readonly previewResourceRoots: vscode.Uri[] = [];
+ readonly markdownItPlugins: Promise<(md: any) => any>[] = [];
+};
export function createNewMarkdownEngine(): MarkdownEngine {
- return new MarkdownEngine(new class implements MarkdownContributions {
- readonly previewScripts: vscode.Uri[] = [];
- readonly previewStyles: vscode.Uri[] = [];
- readonly previewResourceRoots: vscode.Uri[] = [];
- readonly markdownItPlugins: Promise<(md: any) => any>[] = [];
- });
+ return new MarkdownEngine(emptyContributions, githubSlugifier);
}
-
diff --git a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts
index d2f6c180b69df5291a43505583f2b6531d7f9b60..6d6f382ae7e9c27f6ab6e25d05f98b76a9c9576d 100644
--- a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts
+++ b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts
@@ -75,18 +75,35 @@ suite('markdown.TableOfContentsProvider', () => {
assert.strictEqual(await provider.lookup('fo o'), undefined);
});
- test('should normalize special characters #44779', async () => {
+ test('should handle special characters #44779', async () => {
const doc = new InMemoryDocument(testFileName, `# Indentação\n`);
const provider = new TableOfContentsProvider(createNewMarkdownEngine(), doc);
- assert.strictEqual((await provider.lookup('indentacao'))!.line, 0);
+ assert.strictEqual((await provider.lookup('indentação'))!.line, 0);
});
- test('should map special З, #37079', async () => {
- const doc = new InMemoryDocument(testFileName, `### Заголовок Header 3`);
+ test('should handle special characters 2, #48482', async () => {
+ const doc = new InMemoryDocument(testFileName, `# Инструкция - Делай Раз, Делай Два\n`);
const provider = new TableOfContentsProvider(createNewMarkdownEngine(), doc);
- assert.strictEqual((await provider.lookup('Заголовок-header-3'))!.line, 0);
- assert.strictEqual((await provider.lookup('3аголовок-header-3'))!.line, 0);
+ assert.strictEqual((await provider.lookup('инструкция---делай-раз-делай-два'))!.line, 0);
+ });
+
+ test('should handle special characters 3, #37079', async () => {
+ const doc = new InMemoryDocument(testFileName, `## Header 2
+### Header 3
+## Заголовок 2
+### Заголовок 3
+### Заголовок Header 3
+## Заголовок`);
+
+ const provider = new TableOfContentsProvider(createNewMarkdownEngine(), doc);
+
+ assert.strictEqual((await provider.lookup('header-2'))!.line, 0);
+ assert.strictEqual((await provider.lookup('header-3'))!.line, 1);
+ assert.strictEqual((await provider.lookup('Заголовок-2'))!.line, 2);
+ assert.strictEqual((await provider.lookup('Заголовок-3'))!.line, 3);
+ assert.strictEqual((await provider.lookup('Заголовок-header-3'))!.line, 4);
+ assert.strictEqual((await provider.lookup('Заголовок'))!.line, 5);
});
});
diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts
index 45814e12d24d6b98f0f284268c26935fcf801218..e7c4cb9d9c35ea63b5b1c14fa3176a25948df65f 100644
--- a/src/vs/base/common/resources.ts
+++ b/src/vs/base/common/resources.ts
@@ -52,6 +52,13 @@ export function dirname(resource: uri): uri {
});
}
+export function joinPath(resource: uri, pathFragment: string): uri {
+ const joinedPath = paths.join(resource.path || '/', pathFragment);
+ return resource.with({
+ path: joinedPath
+ });
+}
+
export function distinctParents(items: T[], resourceAccessor: (item: T) => uri): T[] {
const distinctParents: T[] = [];
for (let i = 0; i < items.length; i++) {
diff --git a/src/vs/base/node/stats.ts b/src/vs/base/node/stats.ts
index e275f3198e7f5d39753c2bb26946710fb9dc8601..e733e308e70309374e341432bf68007b380ee1e9 100644
--- a/src/vs/base/node/stats.ts
+++ b/src/vs/base/node/stats.ts
@@ -38,21 +38,26 @@ export function collectLaunchConfigs(folder: string): Promise .issue > .issue-state { background-color: ${styles.inputBackground}; }`);
+ content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { background-color: ${styles.inputBackground}; }`);
}
if (styles.inputBorder) {
@@ -160,7 +160,7 @@ export class IssueReporter extends Disposable {
}
if (styles.inputForeground) {
- content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state { color: ${styles.inputForeground}; }`);
+ content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { color: ${styles.inputForeground}; }`);
}
if (styles.inputErrorBorder) {
diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css
index 00e61ad81830ab539fa4aa249f47a55d5fe6c180..aeb02c73f2bd814e5226d69fd77698600c612772 100644
--- a/src/vs/code/electron-browser/issue/media/issueReporter.css
+++ b/src/vs/code/electron-browser/issue/media/issueReporter.css
@@ -10,18 +10,17 @@
table {
width: 100%;
max-width: 100%;
- margin-bottom: 1rem;
background-color: transparent;
border-collapse: collapse;
}
th {
vertical-align: bottom;
- border-bottom: 2px solid #e9ecef;
- padding: .75rem;
+ border-bottom: 1px solid;
+ padding: 5px;
text-align: inherit;
}
td {
- padding: .25rem;
+ padding: 5px;
vertical-align: top;
}
@@ -65,6 +64,7 @@ textarea {
width: auto;
padding: 4px 10px;
align-self: flex-end;
+ margin-bottom: 10px;
}
select {
@@ -75,7 +75,6 @@ select {
line-height: 1.5;
color: #495057;
background-color: #fff;
- border-radius: 0.25rem;
border: none;
}
@@ -95,7 +94,7 @@ html {
body {
margin: 0;
- overflow: scroll;
+ overflow-y: scroll;
height: 100%;
}
@@ -109,16 +108,16 @@ body {
.block .block-info {
width: 100%;
- font-family: 'Menlo', 'Courier New', 'Courier', monospace;
+ font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
font-size: 12px;
overflow: auto;
overflow-wrap: break-word;
+ margin: 5px;
+ padding: 10px;
}
-pre {
- margin: 10px 20px;
-}
+
pre code {
- font-family: 'Menlo', 'Courier New', 'Courier', monospace;
+ font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
}
#issue-reporter {
@@ -126,6 +125,7 @@ pre code {
margin-left: auto;
margin-right: auto;
padding-top: 2em;
+ padding-bottom: 2em;
display: flex;
flex-direction: column;
height: 100%;
@@ -203,7 +203,6 @@ input:disabled {
.instructions {
font-size: 12px;
- margin-left: 1em;
margin-top: .5em;
}
@@ -242,7 +241,7 @@ a {
}
.section .input-group .validation-error {
- margin-left: 13%;
+ margin-left: 15%;
}
.section .inline-form-control, .section .inline-label {
@@ -262,17 +261,25 @@ a {
}
#similar-issues {
- margin-left: 13%;
+ margin-left: 15%;
display: block;
}
+#problem-source-help-text {
+ margin-left: calc(15% + 1em);
+}
+
@media (max-width: 950px) {
.section .inline-label {
- width: 13%;
+ width: 15%;
+ }
+
+ #problem-source-help-text {
+ margin-left: calc(15% + 1em);
}
.section .inline-form-control {
- width: calc(87% - 5px);
+ width: calc(85% - 5px);
}
}
@@ -281,6 +288,10 @@ a {
display: none !important;
}
+ #problem-source-help-text {
+ margin-left: 1em;
+ }
+
.section .inline-form-control {
width: 100%;
}
diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts
index c5c8d20d48b649041cd019194bbdcf7944d1a466..2aae0897c8b6c50672a5e6814a9f0b717b413db6 100644
--- a/src/vs/editor/contrib/suggest/suggestWidget.ts
+++ b/src/vs/editor/contrib/suggest/suggestWidget.ts
@@ -638,7 +638,7 @@ export class SuggestWidget implements IContentWidget, IDelegate
this.show();
break;
case State.Open:
- hide(this.messageElement, this.details.element);
+ hide(this.messageElement);
show(this.listElement);
this.show();
break;
@@ -677,7 +677,10 @@ export class SuggestWidget implements IContentWidget, IDelegate
this.loadingTimeout = null;
}
- this.completionModel = completionModel;
+ if (this.completionModel !== completionModel) {
+ this.completionModel = completionModel;
+ this.focusedItem = null;
+ }
if (isFrozen && this.state !== State.Empty && this.state !== State.Hidden) {
this.setState(State.Frozen);
@@ -712,7 +715,6 @@ export class SuggestWidget implements IContentWidget, IDelegate
*/
this.telemetryService.publicLog('suggestWidget', { ...stats, ...this.editor.getTelemetryData() });
- this.focusedItem = null;
this.list.splice(0, this.list.length, this.completionModel.items);
if (isFrozen) {
diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts
index d8eceb42c9281255b9abd73ec6722040e873b3ed..7723353fc8724972e9e857a48732a2f11c1cfd9c 100644
--- a/src/vs/platform/issue/electron-main/issueService.ts
+++ b/src/vs/platform/issue/electron-main/issueService.ts
@@ -51,7 +51,7 @@ export class IssueService implements IIssueService {
});
this._issueParentWindow = BrowserWindow.getFocusedWindow();
- const position = this.getWindowPosition(this._issueParentWindow, 800, 900);
+ const position = this.getWindowPosition(this._issueParentWindow, 700, 800);
this._issueWindow = new BrowserWindow({
width: position.width,
height: position.height,
diff --git a/src/vs/platform/localizations/common/localizations.ts b/src/vs/platform/localizations/common/localizations.ts
index 5f091c4f6f35d3c671cb086a3029226bd27e84b7..b6361b620a47cb08b48cbd89e41e3afe10d5e7e0 100644
--- a/src/vs/platform/localizations/common/localizations.ts
+++ b/src/vs/platform/localizations/common/localizations.ts
@@ -13,6 +13,7 @@ export interface ILocalization {
languageName?: string;
languageNameLocalized?: string;
translations: ITranslation[];
+ minimalTranslations?: { [key: string]: string };
}
export interface ITranslation {
diff --git a/src/vs/platform/node/minimalTranslations.ts b/src/vs/platform/node/minimalTranslations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..11248d86d88e3dc8cd1d6e0b721a03006019c3d1
--- /dev/null
+++ b/src/vs/platform/node/minimalTranslations.ts
@@ -0,0 +1,18 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { localize } from 'vs/nls';
+
+// The strings localized in this file will get pulled into the manifest of the language packs.
+// So that they are available for VS Code to use without downloading the entire language pack.
+
+export const minimumTranslatedStrings = {
+ showLanguagePackExtensions: localize('showLanguagePackExtensions', "The Marketplace has extensions that can localize VS Code in the {0} language"),
+ searchMarketplace: localize('searchMarketplace', "Search Marketplace"),
+ installAndRestartMessage: localize('installAndRestartMessage', "Install language pack to localize VS Code in {0} language. Restart VS Code after installing for the language to take effect."),
+ installAndRestart: localize('installAndRestart', "Install and Restart"),
+ install: localize('install', 'Install')
+};
+
diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts
index 9199efd1eec718f273ca00e5e20d2d06ec948979..27acdda3489a33c965d731c0e1211206806d1741 100644
--- a/src/vs/vscode.d.ts
+++ b/src/vs/vscode.d.ts
@@ -5266,6 +5266,23 @@ declare module 'vscode' {
readonly webviewPanel: WebviewPanel;
}
+ /**
+ * Restore webview panels that have been persisted when vscode shuts down.
+ */
+ interface WebviewPanelSerializer {
+ /**
+ * Restore a webview panel from its seriailzed `state`.
+ *
+ * Called when a serialized webview first becomes visible.
+ *
+ * @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel.
+ * @param state Persisted state. This state comesfrom the value set inside the webview by `acquireVsCodeApi().setState`.
+ *
+ * @return Thanble indicating that the webview has been fully restored.
+ */
+ deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable;
+ }
+
/**
* Namespace describing the environment the editor runs in.
*/
@@ -5760,6 +5777,19 @@ declare module 'vscode' {
*/
export function createWebviewPanel(viewType: string, title: string, showOptions: ViewColumn | { viewColumn: ViewColumn, preserveFocus?: boolean }, options?: WebviewPanelOptions & WebviewOptions): WebviewPanel;
+ /**
+ * Registers a [webview panel serializer](#WebviewPanelSerializer).
+ *
+ * Extensions that support reviving should have an `"onWebviewPanel:viewType"` activation method and
+ * make sure that [registerWebviewPanelSerializer](#registerWebviewPanelSerializer) is called during activation.
+ *
+ * Only a single serializer may be registered at a time for a given `viewType`.
+ *
+ * @param viewType Type of the webview panel that can be serialized.
+ * @param reviver Webview serializer.
+ */
+ export function registerWebviewPanelSerializer(viewType: string, reviver: WebviewPanelSerializer): Disposable;
+
/**
* Set a message to the status bar. This is a short hand for the more powerful
* status bar [items](#window.createStatusBarItem).
diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts
index b7c2346400e7efe94ea5912bae2e6d5246679622..fe241ee9ade3ff7f9483364f216f8fce72fee415 100644
--- a/src/vs/vscode.proposed.d.ts
+++ b/src/vs/vscode.proposed.d.ts
@@ -296,42 +296,6 @@ declare module 'vscode' {
//#endregion
- //#region Matt: WebView Serializer
-
- /**
- * Restore webview panels that have been persisted when vscode shuts down.
- */
- interface WebviewPanelSerializer {
- /**
- * Restore a webview panel from its seriailzed `state`.
- *
- * Called when a serialized webview first becomes visible.
- *
- * @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel.
- * @param state Persisted state.
- *
- * @return Thanble indicating that the webview has been fully restored.
- */
- deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable;
- }
-
- namespace window {
- /**
- * Registers a webview panel serializer.
- *
- * Extensions that support reviving should have an `"onView:viewType"` activation method and
- * make sure that [registerWebviewPanelSerializer](#registerWebviewPanelSerializer) is called during activation.
- *
- * Only a single serializer may be registered at a time for a given `viewType`.
- *
- * @param viewType Type of the webview panel that can be serialized.
- * @param reviver Webview serializer.
- */
- export function registerWebviewPanelSerializer(viewType: string, reviver: WebviewPanelSerializer): Disposable;
- }
-
- //#endregion
-
//#region Tasks
/**
diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts
index cb968927ce33a694597b36ee5a81cc67a0ca2f74..ae00eadde446749caab57af0c9f662e42b65ea17 100644
--- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts
+++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts
@@ -137,7 +137,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
reviveWebview(webview: WebviewEditorInput): TPromise {
const viewType = webview.state.viewType;
- return this._extensionService.activateByEvent(`onView:${viewType}`).then(() => {
+ return this._extensionService.activateByEvent(`onWebviewPanel:${viewType}`).then(() => {
const handle = 'revival-' + MainThreadWebviews.revivalPool++;
this._webviews.set(handle, webview);
webview._events = this.createWebviewEventDelegate(handle);
diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts
index ff1eec77d1218fe232293f18ac35f2abad4714f3..31ec131d4aa89749a41a0b5f5d25fa6ab84ecdfa 100644
--- a/src/vs/workbench/api/node/extHost.api.impl.ts
+++ b/src/vs/workbench/api/node/extHost.api.impl.ts
@@ -422,6 +422,9 @@ export function createApiFactory(
createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel {
return extHostWebviews.createWebview(viewType, title, showOptions, options, extension.extensionLocation);
},
+ registerWebviewPanelSerializer(viewType: string, serializer: vscode.WebviewPanelSerializer) {
+ return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
+ },
createTerminal(nameOrOptions: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
if (typeof nameOrOptions === 'object') {
return extHostTerminalService.createTerminalFromOptions(nameOrOptions);
@@ -441,9 +444,6 @@ export function createApiFactory(
registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => {
return extHostDecorations.registerDecorationProvider(provider, extension.id);
}),
- registerWebviewPanelSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
- return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
- }),
registerProtocolHandler: proposedApiFunction(extension, (handler: vscode.ProtocolHandler) => {
return extHostUrls.registerProtocolHandler(extension.id, handler);
})
diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts
index 4c0370d20d8dcfb362419194a9bf41cf256ac5be..f2666010f655a5c937a23b6da7165f0f24e913e2 100644
--- a/src/vs/workbench/api/node/extHostSearch.ts
+++ b/src/vs/workbench/api/node/extHostSearch.ts
@@ -18,6 +18,7 @@ import { ICachedSearchStats, IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQ
import * as vscode from 'vscode';
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
+import { joinPath } from 'vs/base/common/resources';
type OneOrMore = T | T[];
@@ -151,7 +152,7 @@ class TextSearchResultsCollector {
}
if (!this._currentFileMatch) {
- const resource = URI.file(path.join(this.folderQueries[folderIdx].folder.fsPath, data.path));
+ const resource = joinPath(this.folderQueries[folderIdx].folder, data.path);
this._currentFileMatch = {
resource,
lineMatches: []
@@ -271,7 +272,7 @@ class BatchedCollector {
}
interface IDirectoryEntry {
- base: string;
+ base: URI;
relativePath: string;
basename: string;
}
@@ -282,8 +283,8 @@ interface IDirectoryTree {
}
interface IInternalFileMatch {
- base?: string;
- relativePath: string; // Not necessarily relative... extraFiles put an absolute path here. Rename.
+ base: URI;
+ relativePath?: string; // Not present for extraFiles or absolute path matches
basename: string;
size?: number;
}
@@ -433,9 +434,9 @@ class TextSearchEngine {
const testingPs = [];
const progress = {
report: (result: vscode.TextSearchResult) => {
- const siblingFn = () => {
+ const siblingFn = folderQuery.folder.scheme === 'file' && (() => {
return this.readdir(path.dirname(path.join(folderQuery.folder.fsPath, result.path)));
- };
+ });
testingPs.push(
queryTester.includedInQuery(result.path, path.basename(result.path), siblingFn)
@@ -558,7 +559,7 @@ class FileSearchEngine {
// Report result from file pattern if matching
if (exists) {
onResult({
- relativePath: this.filePattern,
+ base: URI.file(this.filePattern),
basename: path.basename(this.filePattern),
size
});
@@ -572,15 +573,15 @@ class FileSearchEngine {
// For each extra file
if (this.config.extraFileResources) {
this.config.extraFileResources
- .map(uri => uri.toString())
- .forEach(extraFilePath => {
- const basename = path.basename(extraFilePath);
- if (this.globalExcludePattern && this.globalExcludePattern(extraFilePath, basename)) {
+ .forEach(extraFile => {
+ const extraFileStr = extraFile.toString(); // ?
+ const basename = path.basename(extraFileStr);
+ if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
return; // excluded
}
// File: Check for match on file pattern and include pattern
- this.matchFile(onResult, { relativePath: extraFilePath /* no workspace relative path */, basename });
+ this.matchFile(onResult, { base: extraFile, basename });
});
}
@@ -604,7 +605,6 @@ class FileSearchEngine {
let cancellation = new CancellationTokenSource();
return new PPromise((resolve, reject, onResult) => {
const options = this.getSearchOptionsForFolder(fq);
- const folderStr = fq.folder.fsPath;
let filePatternSeen = false;
const tree = this.initDirectoryTree();
@@ -616,20 +616,19 @@ class FileSearchEngine {
return;
}
- // This is slow...
if (noSiblingsClauses) {
if (relativePath === this.filePattern) {
filePatternSeen = true;
}
const basename = path.basename(relativePath);
- this.matchFile(onResult, { base: folderStr, relativePath, basename });
+ this.matchFile(onResult, { base: fq.folder, relativePath, basename });
return;
}
// TODO: Optimize siblings clauses with ripgrep here.
- this.addDirectoryEntries(tree, folderStr, relativePath, onResult);
+ this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
};
new TPromise(resolve => process.nextTick(resolve))
@@ -646,10 +645,10 @@ class FileSearchEngine {
if (noSiblingsClauses && this.isLimitHit) {
if (!filePatternSeen) {
// If the limit was hit, check whether filePattern is an exact relative match because it must be included
- return this.checkFilePatternRelativeMatch(folderStr).then(({ exists, size }) => {
+ return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => {
if (exists) {
onResult({
- base: folderStr,
+ base: fq.folder,
relativePath: this.filePattern,
basename: path.basename(this.filePattern),
});
@@ -658,7 +657,7 @@ class FileSearchEngine {
}
}
- this.matchDirectoryTree(tree, folderStr, queryTester, onResult);
+ this.matchDirectoryTree(tree, queryTester, onResult);
return null;
}).then(
() => {
@@ -694,7 +693,7 @@ class FileSearchEngine {
return tree;
}
- private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: string, relativeFile: string, onResult: (result: IInternalFileMatch) => void) {
+ private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: URI, relativeFile: string, onResult: (result: IInternalFileMatch) => void) {
// Support relative paths to files from a root resource (ignores excludes)
if (relativeFile === this.filePattern) {
const basename = path.basename(this.filePattern);
@@ -719,7 +718,7 @@ class FileSearchEngine {
add(relativeFile);
}
- private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, rootFolder: string, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) {
+ private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) {
const self = this;
const filePattern = this.filePattern;
function matchDirectory(entries: IDirectoryEntry[]) {
@@ -794,12 +793,12 @@ class FileSearchEngine {
});
}
- private checkFilePatternRelativeMatch(basePath: string): TPromise<{ exists: boolean, size?: number }> {
- if (!this.filePattern || path.isAbsolute(this.filePattern)) {
+ private checkFilePatternRelativeMatch(base: URI): TPromise<{ exists: boolean, size?: number }> {
+ if (!this.filePattern || path.isAbsolute(this.filePattern) || base.scheme !== 'file') {
return TPromise.wrap({ exists: false });
}
- const absolutePath = path.join(basePath, this.filePattern);
+ const absolutePath = path.join(base.fsPath, this.filePattern);
return this._pfs.stat(absolutePath).then(stat => {
return {
exists: !stat.isDirectory(),
@@ -894,7 +893,7 @@ class FileSearchManager {
private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch {
return {
- resource: URI.file(match.base ? path.join(match.base, match.relativePath) : match.relativePath)
+ resource: joinPath(match.base, match.relativePath)
};
}
diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts
index 8fcfc3f1c499235c2048895ed259d8da0f1d1d49..40431d62f5e8acc00e73f15d7aef91c8f02ed8d4 100644
--- a/src/vs/workbench/browser/parts/views/customView.ts
+++ b/src/vs/workbench/browser/parts/views/customView.ts
@@ -63,8 +63,9 @@ export class CustomViewsService extends Disposable implements IViewsService {
return this.viewletService.openViewlet(viewletDescriptor.id)
.then((viewlet: IViewsViewlet) => {
if (viewlet && viewlet.openView) {
- viewlet.openView(id, focus);
+ return viewlet.openView(id, focus);
}
+ return null;
});
}
}
@@ -280,17 +281,21 @@ class CustomTreeViewer extends Disposable implements ITreeViewer {
reveal(item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean }): TPromise {
if (this.tree && this.isVisible) {
options = options ? options : { select: true };
- const select = isUndefinedOrNull(options.select) ? true : options.select;
- var result = TPromise.as(null);
- parentChain.forEach((e) => {
- result = result.then(() => this.tree.expand(e));
- });
- return result.then(() => this.tree.reveal(item))
- .then(() => {
- if (select) {
- this.tree.setSelection([item], { source: 'api' });
- }
+ const root: Root = this.tree.getInput();
+ const promise = root.children ? TPromise.as(null) : this.refresh(); // Refresh if root is not populated
+ return promise.then(() => {
+ const select = isUndefinedOrNull(options.select) ? true : options.select;
+ var result = TPromise.as(null);
+ parentChain.forEach((e) => {
+ result = result.then(() => this.tree.expand(e));
});
+ return result.then(() => this.tree.reveal(item))
+ .then(() => {
+ if (select) {
+ this.tree.setSelection([item], { source: 'api' });
+ }
+ });
+ });
}
return TPromise.as(null);
}
diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts
index 8736c448bc0e546bc274130beda41e17b4be9492..315700baf842e468b2171ca70e0e1f0e7db502f9 100644
--- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts
+++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts
@@ -34,7 +34,6 @@ import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspace
import { IRequestService } from 'vs/platform/request/node/request';
import { asJson } from 'vs/base/node/request';
import { isNumber } from 'vs/base/common/types';
-import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -46,7 +45,6 @@ const empty: { [key: string]: any; } = Object.create(null);
const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't Show Again");
const searchMarketplace = localize('searchMarketplace', "Search Marketplace");
-const coreLanguages = ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'tr', 'zh-cn', 'zh-tw'];
interface IDynamicWorkspaceRecommendations {
remoteSet: string[];
@@ -93,7 +91,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl;
}
- this.getLanguageExtensionRecommendations();
this.getCachedDynamicWorkspaceRecommendations();
this._suggestFileBasedRecommendations();
this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations();
@@ -132,90 +129,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return this._galleryService.isEnabled() && !this.environmentService.extensionDevelopmentPath;
}
- private getLanguageExtensionRecommendations() {
- const config = this.configurationService.getValue(ConfigurationKey);
- const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get
- ('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]'));
-
- if (!language
- || language === LANGUAGE_DEFAULT
- || coreLanguages.some(x => language === x || language.indexOf(x + '-') === 0)
- || config.ignoreRecommendations
- || config.showRecommendationsOnlyOnDemand
- || languagePackSuggestionIgnoreList.indexOf(language) > -1) {
- return;
- }
-
- this.extensionsService.getInstalled(LocalExtensionType.User).then(locals => {
- for (var i = 0; i < locals.length; i++) {
- if (locals[i].manifest
- && locals[i].manifest.contributes
- && Array.isArray(locals[i].manifest.contributes.localizations)
- && locals[i].manifest.contributes.localizations.some(x => x.languageId === language)) {
- return;
- }
- }
-
- this._galleryService.query({ text: `tag:lp-${language}` }).then(pager => {
- if (!pager || !pager.firstPage || !pager.firstPage.length) {
- return;
- }
-
- this.notificationService.prompt(
- Severity.Info,
- localize('showLanguagePackExtensions', "The Marketplace has extensions that can help localizing VS Code to '{0}' locale", language),
- [{
- label: searchMarketplace,
- run: () => {
- /* __GDPR__
- "languagePackSuggestion:popup" : {
- "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
- "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
- }
- */
- this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'ok', language });
- this.viewletService.openViewlet('workbench.view.extensions', true)
- .then(viewlet => viewlet as IExtensionsViewlet)
- .then(viewlet => {
- viewlet.search(`tag:lp-${language}`);
- viewlet.focus();
- });
- }
- },
- {
- label: choiceNever,
- isSecondary: true,
- run: () => {
- languagePackSuggestionIgnoreList.push(language);
- this.storageService.store(
- 'extensionsAssistant/languagePackSuggestionIgnore',
- JSON.stringify(languagePackSuggestionIgnoreList),
- StorageScope.GLOBAL
- );
- /* __GDPR__
- "languagePackSuggestion:popup" : {
- "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
- "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
- }
- */
- this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'neverShowAgain', language });
- }
- }],
- () => {
- /* __GDPR__
- "languagePackSuggestion:popup" : {
- "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
- "language": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
- }
- */
- this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'cancelled', language });
- }
- );
- });
- });
- }
-
-
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } {
let output: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } = Object.create(null);
diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts
index 7e9a9f3ffe4a8890cda60b55713047f68800e364..6bc4493acf9802fbce70945300038e9e8c11bd11 100644
--- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts
+++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts
@@ -23,11 +23,12 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import URI from 'vs/base/common/uri';
import { join } from 'vs/base/common/paths';
import { IWindowsService } from 'vs/platform/windows/common/windows';
-import { IStorageService, StorageScope, } from 'vs/platform/storage/common/storage';
+import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { TPromise } from 'vs/base/common/winjs.base';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions';
-import product from 'vs/platform/node/product';
+import { minimumTranslatedStrings } from 'vs/platform/node/minimalTranslations';
+import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
// Register action to configure locale and related settings
const registry = Registry.as(Extensions.WorkbenchActions);
@@ -43,7 +44,8 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
@IStorageService private storageService: IStorageService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
- @IViewletService private viewletService: IViewletService
+ @IViewletService private viewletService: IViewletService,
+ @ITelemetryService private telemetryService: ITelemetryService
) {
super();
this.updateLocaleDefintionSchema();
@@ -96,6 +98,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
private checkAndInstall(): void {
const language = platform.language;
+ const locale = platform.locale;
if (language !== 'en' && language !== 'en_us') {
this.isLanguageInstalled(language)
.then(installed => {
@@ -115,53 +118,116 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
return;
}
- const bundledTranslations = (product['bundledTranslations'] || {})[platform.locale];
- if (language === platform.locale || !bundledTranslations || !bundledTranslations['languageName']) {
+ const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get
+ ('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]'));
+
+ if (language === locale || languagePackSuggestionIgnoreList.indexOf(language) > -1) {
return;
}
- // The initial value for below dont get used. We just have it here so that they get localized.
- // The localized strings get pulled into the "product.json" file during endgame to get shipped
- let searchForLanguagePacks = localize('searchForLanguagePacks', "There are extensions in the Marketplace that can localize VS Code using the ${0} language.", bundledTranslations['languageName']);
- let searchMarketplace = localize('searchMarketplace', "Search Marketplace");
- let dontShowAgain = localize('neverAgain', "Don't Show Again");
+ this.isLanguageInstalled(locale)
+ .then(installed => {
+ if (installed) {
+ return;
+ }
- searchForLanguagePacks = bundledTranslations['searchForLanguagePacks'];
- searchMarketplace = bundledTranslations['searchMarketplace'];
- dontShowAgain = bundledTranslations['neverAgain'];
+ const ceintlExtensionSearch = this.galleryService.query({ names: [`MS-CEINTL.vscode-language-pack-${locale}`], pageSize: 1 });
+ const tagSearch = this.galleryService.query({ text: `tag:lp-${locale}`, pageSize: 1 });
- const dontShowSearchLanguagePacksAgainKey = 'language.install.donotask';
- let dontShowSearchForLanguages = JSON.parse(this.storageService.get(dontShowSearchLanguagePacksAgainKey, StorageScope.GLOBAL, '[]'));
- if (!Array.isArray(dontShowSearchForLanguages)) {
- dontShowSearchForLanguages = [];
- }
+ TPromise.join([ceintlExtensionSearch, tagSearch]).then(([ceintlResult, tagResult]) => {
+ if (ceintlResult.total === 0 && tagResult.total === 0) {
+ return;
+ }
- if (dontShowSearchForLanguages.indexOf(platform.locale) > -1
- || !searchForLanguagePacks
- || !searchMarketplace
- || !dontShowAgain) {
- return;
- }
+ const extensionToInstall = ceintlResult.total === 1 ? ceintlResult.firstPage[0] : tagResult.total === 1 ? tagResult.firstPage[0] : null;
+ const extensionToFetchTranslationsFrom = extensionToInstall || tagResult.total > 0 ? tagResult.firstPage[0] : null;
- this.notificationService.prompt(Severity.Info, searchForLanguagePacks,
- [
- {
- label: searchMarketplace, run: () => {
- this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true)
- .then(viewlet => viewlet as IExtensionsViewlet)
- .then(viewlet => {
- viewlet.search(`tag:lp-${platform.locale}`);
- viewlet.focus();
- });
+ if (!extensionToFetchTranslationsFrom || !extensionToFetchTranslationsFrom.assets.manifest) {
+ return;
}
- },
- {
- label: dontShowAgain, run: () => {
- dontShowSearchForLanguages.push(language);
- this.storageService.store(dontShowSearchLanguagePacksAgainKey, StorageScope.GLOBAL, dontShowSearchForLanguages);
- }
- }
- ]);
+
+ this.galleryService.getManifest(extensionToFetchTranslationsFrom).then(x => {
+ if (!x.contributes || !x.contributes.localizations) {
+ return;
+ }
+ const locContribution = x.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0];
+ if (!locContribution) {
+ return;
+ }
+
+ const translations = {
+ ...minimumTranslatedStrings,
+ ...(locContribution.minimalTranslations || {})
+ };
+
+ const logUserReaction = (userReaction: string) => {
+ /* __GDPR__
+ "languagePackSuggestion:popup" : {
+ "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+ }
+ */
+ this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction, language });
+ };
+
+ const searchAction = {
+ label: translations['searchMarketplace'],
+ run: () => {
+ logUserReaction('search');
+ this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true)
+ .then(viewlet => viewlet as IExtensionsViewlet)
+ .then(viewlet => {
+ viewlet.search(`tag:lp-${locale}`);
+ viewlet.focus();
+ });
+ }
+ };
+
+ const installAction = {
+ label: translations['install'],
+ run: () => {
+ logUserReaction('install');
+ this.installExtension(extensionToInstall);
+ }
+ };
+
+ const installAndRestartAction = {
+ label: translations['installAndRestart'],
+ run: () => {
+ logUserReaction('installAndRestart');
+ this.installExtension(extensionToInstall).then(() => this.windowsService.relaunch({}));
+ }
+ };
+
+ const mainActions = extensionToInstall ? [installAndRestartAction, installAction] : [searchAction];
+ const promptMessage = translations[extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions']
+ .replace('{0}', locContribution.languageNameLocalized || locContribution.languageName || locale);
+
+ this.notificationService.prompt(
+ Severity.Info,
+ promptMessage,
+ [...mainActions,
+ {
+ label: localize('neverAgain', "Don't Show Again"),
+ isSecondary: true,
+ run: () => {
+ languagePackSuggestionIgnoreList.push(language);
+ this.storageService.store(
+ 'extensionsAssistant/languagePackSuggestionIgnore',
+ JSON.stringify(languagePackSuggestionIgnoreList),
+ StorageScope.GLOBAL
+ );
+ logUserReaction('neverShowAgain');
+ }
+ }],
+ () => {
+ logUserReaction('cancelled');
+ }
+ );
+
+ });
+ });
+ });
}
diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css
index 51a944d9908b521675905993b1a865163900006c..688fc6a36d94d5514f6bfe043fd05e521a2836e3 100644
--- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css
+++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css
@@ -4,14 +4,17 @@
*--------------------------------------------------------------------------------------------*/
.settings-editor {
- padding: 11px 0px 0px 27px;
+ padding-top: 11px;
margin: auto;
}
/* header styling */
-
.settings-editor > .settings-header {
- padding: 0px 10px 0px 0px;
+ padding-left: 15px;
+ padding-right: 5px;
+ max-width: 800px;
+ box-sizing: border-box;
+ margin: auto;
}
.settings-editor > .settings-header > .settings-preview-header {
@@ -22,14 +25,19 @@
opacity: .7;
}
-.settings-editor > .settings-header > .settings-preview-header .open-settings-button,
-.settings-editor > .settings-header > .settings-preview-header .open-settings-button:hover,
-.settings-editor > .settings-header > .settings-preview-header .open-settings-button:active {
+.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button,
+.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button:hover,
+.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button:active {
padding: 0;
text-decoration: underline;
display: inline;
}
+.settings-editor > .settings-header > .settings-advanced-customization {
+ opacity: .7;
+ margin-top: 10px;
+}
+
.settings-editor > .settings-header > .settings-preview-header > .settings-preview-warning {
text-align: right;
text-transform: uppercase;
@@ -59,7 +67,7 @@
}
.settings-editor > .settings-header > .settings-header-controls {
- margin-top: 7px;
+ margin-top: 2px;
height: 30px;
display: flex;
}
@@ -87,6 +95,7 @@
white-space: nowrap;
margin-right: 10px;
margin-left: 2px;
+ opacity: 0.7;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree-wrapper {
@@ -122,16 +131,6 @@
min-height: 75px;
}
-.settings-editor > .settings-body > .settings-tree-container .monaco-tree-row .content::before {
- content: ' ';
- display: inline-block;
- position: absolute;
- width: 5px;
- left: -9px;
- top: 2px;
- bottom: 10px;
-}
-
.settings-editor > .settings-body > .settings-tree-container .setting-item.odd:not(.focused):not(.selected):not(:hover),
.settings-editor > .settings-body > .settings-tree-container .monaco-tree:not(:focus) .setting-item.focused.odd:not(.selected):not(:hover),
.settings-editor > .settings-body > .settings-tree-container .monaco-tree:not(.focused) .setting-item.focused.odd:not(.selected):not(:hover) {
@@ -146,37 +145,37 @@
.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-right {
min-width: 180px;
- margin: 21px 10px 0px;
+ margin: 21px 10px 0px 5px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title {
line-height: initial;
}
+.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-is-configured-label {
+ font-style: italic;
+ opacity: 0.8;
+ margin-right: 7px;
+}
+
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-overrides {
opacity: 0.5;
- margin-left: 7px;
font-style: italic;
}
+.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label {
+ margin-right: 7px;
+}
+
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label,
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category {
font-weight: bold;
- font-size: 14px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category {
opacity: 0.7;
}
-.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-key {
- margin-left: 10px;
- font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
- font-size: 90%;
- opacity: 0.8;
- display: none;
-}
-
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description {
opacity: 0.7;
margin-top: 3px;
@@ -196,14 +195,6 @@
height: initial;
}
-.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expandable .setting-item-description {
- cursor: pointer;
-}
-
-.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value {
- display: flex;
-}
-
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button,
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button:hover,
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button:active {
@@ -222,28 +213,16 @@
height: 26px;
}
-.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-value-checkbox {
- position: relative;
-}
-
-.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-value-checkbox::after {
- content: ' ';
+.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button {
+ text-align: left;
display: block;
- height: 3px;
- width: 18px;
- position: absolute;
- top: 15px;
- left: -3px;
+ visibility: hidden;
+
+ padding-top: 0px; /* So focus outline doesn't overlap the control above */
}
-.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button {
- display: inline-block;
- background: url("clean.svg") center center no-repeat;
- width: 16px;
- height: 16px;
- margin: auto;
- margin-left: 3px;
- visibility: hidden;
+.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-value > .setting-reset-button.monaco-button {
+ visibility: visible;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .expand-indicator {
@@ -259,14 +238,6 @@
visibility: visible;
}
-.vs-dark .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button {
- background: url("clean-dark.svg") center center no-repeat;
-}
-
-.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-value > .setting-reset-button.monaco-button {
- visibility: visible;
-}
-
.settings-editor > .settings-body > .settings-tree-container .all-settings {
display: flex;
}
@@ -292,13 +263,10 @@
overflow: visible;
}
-.settings-editor .settings-body {
- margin-left: -15px;
-}
-
.settings-editor > .settings-body > .settings-tree-container .settings-group-title-label {
margin: 0px;
padding: 5px 0px;
+ font-size: 13px;
}
.settings-editor > .settings-body .settings-feedback-button {
diff --git a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts
index b40d4055be583a49bdd60fbf08fde0a0ed75ba88..ecf92c2c9bab97ef9e5496fa9d839e1da5bb9c01 100644
--- a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts
+++ b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts
@@ -55,7 +55,7 @@ export class OpenRawUserSettingsAction extends Action {
export class OpenSettings2Action extends Action {
public static readonly ID = 'workbench.action.openSettings2';
- public static readonly LABEL = nls.localize('openSettings2', "Open Settings (Experimental)");
+ public static readonly LABEL = nls.localize('openSettings2', "Open Settings (Preview)");
constructor(
id: string,
diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts
index bf39855a2fa6f0aa55df4011d2cf0df2bb2f1fab..34c8bc9b29ece87a6a5dc6a473662578546bdaf6 100644
--- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts
+++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts
@@ -20,7 +20,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { ILogService } from 'vs/platform/log/common/log';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
-import { editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry';
+import { editorBackground, foreground, listActiveSelectionBackground, listInactiveSelectionBackground } from 'vs/platform/theme/common/colorRegistry';
import { attachButtonStyler, attachStyler } from 'vs/platform/theme/common/styler';
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
@@ -46,6 +46,7 @@ export class SettingsEditor2 extends BaseEditor {
private settingsTargetsWidget: SettingsTargetsWidget;
private showConfiguredSettingsOnlyCheckbox: HTMLInputElement;
+ private savedExpandedGroups: any[];
private settingsTreeContainer: HTMLElement;
private settingsTree: WorkbenchTree;
@@ -114,17 +115,7 @@ export class SettingsEditor2 extends BaseEditor {
previewAlert.textContent = localize('previewWarning', "Preview");
const previewTextLabel = DOM.append(previewHeader, $('span.settings-preview-label'));
- previewTextLabel.textContent = localize('previewLabel', "This is a preview of our new settings editor. You can also ");
- const openSettingsButton = this._register(new Button(previewHeader, { title: true, buttonBackground: null, buttonHoverBackground: null }));
- this._register(attachButtonStyler(openSettingsButton, this.themeService, {
- buttonBackground: Color.transparent.toString(),
- buttonHoverBackground: Color.transparent.toString(),
- buttonForeground: 'foreground'
- }));
- openSettingsButton.label = localize('openSettingsLabel', "open the original editor.");
- openSettingsButton.element.classList.add('open-settings-button');
-
- this._register(openSettingsButton.onDidClick(() => this.openSettingsFile()));
+ previewTextLabel.textContent = localize('previewLabel', "This is a preview of our new settings editor");
const searchContainer = DOM.append(this.headerContainer, $('.search-container'));
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, {
@@ -139,6 +130,20 @@ export class SettingsEditor2 extends BaseEditor {
}
}));
+ const advancedCustomization = DOM.append(this.headerContainer, $('.settings-advanced-customization'));
+ const advancedCustomizationLabel = DOM.append(advancedCustomization, $('span.settings-advanced-customization-label'));
+ advancedCustomizationLabel.textContent = localize('advancedCustomizationLabel', "For advanced customizations open and edit") + ' ';
+ const openSettingsButton = this._register(new Button(advancedCustomization, { title: true, buttonBackground: null, buttonHoverBackground: null }));
+ this._register(attachButtonStyler(openSettingsButton, this.themeService, {
+ buttonBackground: Color.transparent.toString(),
+ buttonHoverBackground: Color.transparent.toString(),
+ buttonForeground: foreground
+ }));
+ openSettingsButton.label = localize('openSettingsLabel', "settings.json");
+ openSettingsButton.element.classList.add('open-settings-button');
+
+ this._register(openSettingsButton.onDidClick(() => this.openSettingsFile()));
+
const headerControlsContainer = DOM.append(this.headerContainer, $('.settings-header-controls'));
const targetWidgetContainer = DOM.append(headerControlsContainer, $('.settings-target-container'));
this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, targetWidgetContainer));
@@ -186,9 +191,8 @@ export class SettingsEditor2 extends BaseEditor {
this.settingsTreeContainer = DOM.append(parent, $('.settings-tree-container'));
this.treeDataSource = this.instantiationService.createInstance(SettingsDataSource, this.viewState);
- const renderer = this.instantiationService.createInstance(SettingsRenderer, this.viewState, this.settingsTreeContainer);
+ const renderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer);
this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value)));
- this._register(renderer.onDidClickButton(e => this.onDidClickShowAllSettings()));
const treeClass = 'settings-editor-tree';
this.settingsTree = this.instantiationService.createInstance(WorkbenchTree, this.settingsTreeContainer,
@@ -208,14 +212,14 @@ export class SettingsEditor2 extends BaseEditor {
});
this._register(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
- const activeListBackground = theme.getColor('list.activeSelectionBackground');
- if (activeListBackground) {
- collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree.focused .monaco-tree-row.focused .content::before { background-color: ${activeListBackground}; }`);
+ const activeBorderColor = theme.getColor(listActiveSelectionBackground);
+ if (activeBorderColor) {
+ collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`);
}
- const inactiveListBackground = theme.getColor('list.inactiveSelectionBackground');
- if (inactiveListBackground) {
- collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused .content::before { background-color: ${inactiveListBackground}; }`);
+ const inactiveBorderColor = theme.getColor(listInactiveSelectionBackground);
+ if (inactiveBorderColor) {
+ collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused {outline: solid 1px ${inactiveBorderColor}; outline-offset: -1px; }`);
}
}));
@@ -265,6 +269,25 @@ export class SettingsEditor2 extends BaseEditor {
private onShowConfiguredOnlyClicked(): void {
this.viewState.showConfiguredOnly = this.showConfiguredSettingsOnlyCheckbox.checked;
this.refreshTree();
+
+ // TODO@roblou - This is slow
+ if (this.viewState.showConfiguredOnly) {
+ this.savedExpandedGroups = this.settingsTree.getExpandedElements();
+ const nav = this.settingsTree.getNavigator();
+ let element;
+ while (element = nav.next()) {
+ this.settingsTree.expand(element);
+ }
+ } else if (this.savedExpandedGroups) {
+ const nav = this.settingsTree.getNavigator();
+ let element;
+ while (element = nav.next()) {
+ this.settingsTree.collapse(element);
+ }
+
+ this.settingsTree.expandAll(this.savedExpandedGroups);
+ this.savedExpandedGroups = null;
+ }
}
private onDidChangeSetting(key: string, value: any): void {
@@ -292,11 +315,6 @@ export class SettingsEditor2 extends BaseEditor {
this.delayedModifyLogging.trigger(() => this.reportModifiedSetting(reportModifiedProps));
}
- private onDidClickShowAllSettings(): void {
- this.viewState.showAllSettings = !this.viewState.showAllSettings;
- this.refreshTree();
- }
-
private reportModifiedSetting(props: { key: string, query: string, searchResults: ISearchResult[], rawResults: ISearchResult[], showConfiguredOnly: boolean, isReset: boolean, settingsTarget: SettingsTarget }): void {
this.pendingSettingModifiedReport = null;
diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts
index 30b3174d4dce51c10f01237516832889b71026f0..02462d73598220031041741f08ff7d5c05be7d2f 100644
--- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts
+++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
+import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { Button } from 'vs/base/browser/ui/button/button';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
@@ -12,19 +13,19 @@ import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects';
+import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IAccessibilityProvider, IDataSource, IFilter, IRenderer, ITree } from 'vs/base/parts/tree/browser/tree';
import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
-import { registerColor } from 'vs/platform/theme/common/colorRegistry';
+import { editorActiveLinkForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { ISearchResult, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
-import { IMouseEvent } from 'vs/base/browser/mouseEvent';
const $ = DOM.$;
@@ -37,7 +38,7 @@ export const modifiedItemForeground = registerColor('settings.modifiedItemForegr
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const modifiedItemForegroundColor = theme.getColor(modifiedItemForeground);
if (modifiedItemForegroundColor) {
- collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-title { color: ${modifiedItemForegroundColor}; }`);
+ collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-is-configured-label { color: ${modifiedItemForegroundColor}; }`);
}
});
@@ -47,8 +48,7 @@ export interface ITreeItem {
export enum TreeItemType {
setting,
- groupTitle,
- buttonRow
+ groupTitle
}
export interface ISettingElement extends ITreeItem {
@@ -73,15 +73,20 @@ export interface IGroupElement extends ITreeItem {
index: number;
}
-const ALL_SETTINGS_BUTTON_ID = 'all_settings_button_row';
-export interface IButtonElement extends ITreeItem {
- type: TreeItemType.buttonRow;
- parent: DefaultSettingsEditorModel;
-}
-
-export type TreeElement = ISettingElement | IGroupElement | IButtonElement;
+export type TreeElement = ISettingElement | IGroupElement;
export type TreeElementOrRoot = TreeElement | DefaultSettingsEditorModel | SearchResultModel;
+function inspectSetting(key: string, target: SettingsTarget, configurationService: IConfigurationService): { isConfigured: boolean, inspected: any, targetSelector: string } {
+ const inspectOverrides = URI.isUri(target) ? { resource: target } : undefined;
+ const inspected = configurationService.inspect(key, inspectOverrides);
+ const targetSelector = target === ConfigurationTarget.USER ? 'user' :
+ target === ConfigurationTarget.WORKSPACE ? 'workspace' :
+ 'workspaceFolder';
+ const isConfigured = typeof inspected[targetSelector] !== 'undefined';
+
+ return { isConfigured, inspected, targetSelector };
+}
+
export class SettingsDataSource implements IDataSource {
constructor(
private viewState: ISettingsEditorViewState,
@@ -98,9 +103,8 @@ export class SettingsDataSource implements IDataSource {
}
getSettingElement(setting: ISetting, group: ISettingsGroup): ISettingElement {
- const targetSelector = this.viewState.settingsTarget === ConfigurationTarget.USER ? 'user' : 'workspace';
- const inspected = this.configurationService.inspect(setting.key);
- const isConfigured = typeof inspected[targetSelector] !== 'undefined';
+ const { isConfigured, inspected, targetSelector } = inspectSetting(setting.key, this.viewState.settingsTarget, this.configurationService);
+
const displayValue = isConfigured ? inspected[targetSelector] : inspected.default;
const overriddenScopeList = [];
if (targetSelector === 'user' && typeof inspected.workspace !== 'undefined') {
@@ -169,16 +173,8 @@ export class SettingsDataSource implements IDataSource {
}
private getRootChildren(root: DefaultSettingsEditorModel): TreeElement[] {
- const groupItems: TreeElement[] = root.settingsGroups
+ return root.settingsGroups
.map((g, i) => this.getGroupElement(g, i));
-
- groupItems.splice(1, 0, {
- id: ALL_SETTINGS_BUTTON_ID,
- type: TreeItemType.buttonRow,
- parent: root
- });
-
- return groupItems;
}
private getGroupChildren(group: ISettingsGroup): ISettingElement[] {
@@ -224,7 +220,6 @@ export function settingKeyToDisplayFormat(key: string): { category: string, labe
export interface ISettingsEditorViewState {
settingsTarget: SettingsTarget;
showConfiguredOnly?: boolean;
- showAllSettings?: boolean;
}
export interface IDisposableTemplate {
@@ -241,7 +236,8 @@ export interface ISettingItemTemplate extends IDisposableTemplate {
descriptionElement: HTMLElement;
expandIndicatorElement: HTMLElement;
valueElement: HTMLElement;
- overridesElement: HTMLElement;
+ isConfiguredElement: HTMLElement;
+ otherOverridesElement: HTMLElement;
}
export interface IGroupTitleTemplate extends IDisposableTemplate {
@@ -250,16 +246,8 @@ export interface IGroupTitleTemplate extends IDisposableTemplate {
labelElement: HTMLElement;
}
-export interface IButtonRowTemplate extends IDisposableTemplate {
- parent: HTMLElement;
-
- button: Button;
- entry?: IButtonElement;
-}
-
const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.entry.template';
const SETTINGS_GROUP_ELEMENT_TEMPLATE_ID = 'settings.group.template';
-const BUTTON_ROW_ELEMENT_TEMPLATE = 'settings.buttonRow.template';
export interface ISettingChangeEvent {
key: string;
@@ -270,9 +258,6 @@ export class SettingsRenderer implements IRenderer {
private static readonly SETTING_ROW_HEIGHT = 75;
- private readonly _onDidClickButton: Emitter = new Emitter();
- public readonly onDidClickButton: Event = this._onDidClickButton.event;
-
private readonly _onDidChangeSetting: Emitter = new Emitter();
public readonly onDidChangeSetting: Event = this._onDidChangeSetting.event;
@@ -282,7 +267,6 @@ export class SettingsRenderer implements IRenderer {
private measureContainer: HTMLElement;
constructor(
- private viewState: ISettingsEditorViewState,
_measureContainer: HTMLElement,
@IThemeService private themeService: IThemeService,
@IContextViewService private contextViewService: IContextViewService
@@ -304,10 +288,6 @@ export class SettingsRenderer implements IRenderer {
}
}
- if (element.type === TreeItemType.buttonRow) {
- return 60;
- }
-
return 0;
}
@@ -327,10 +307,6 @@ export class SettingsRenderer implements IRenderer {
return SETTINGS_GROUP_ELEMENT_TEMPLATE_ID;
}
- if (element.type === TreeItemType.buttonRow) {
- return BUTTON_ROW_ELEMENT_TEMPLATE;
- }
-
if (element.type === TreeItemType.setting) {
return SETTINGS_ELEMENT_TEMPLATE_ID;
}
@@ -343,10 +319,6 @@ export class SettingsRenderer implements IRenderer {
return this.renderGroupTitleTemplate(container);
}
- if (templateId === BUTTON_ROW_ELEMENT_TEMPLATE) {
- return this.renderButtonRowTemplate(container);
- }
-
if (templateId === SETTINGS_ELEMENT_TEMPLATE_ID) {
return this.renderSettingTemplate(container);
}
@@ -369,26 +341,6 @@ export class SettingsRenderer implements IRenderer {
return template;
}
- private renderButtonRowTemplate(container: HTMLElement): IButtonRowTemplate {
- DOM.addClass(container, 'all-settings');
-
- const buttonElement = DOM.append(container, $('.all-settings-button'));
-
- const button = new Button(buttonElement);
- const toDispose: IDisposable[] = [button];
-
- const template: IButtonRowTemplate = {
- parent: container,
- toDispose,
-
- button
- };
- template.toDispose.push(attachButtonStyler(button, this.themeService));
- template.toDispose.push(button.onDidClick(e => this._onDidClickButton.fire(template.entry && template.entry.id)));
-
- return template;
- }
-
private renderSettingTemplate(container: HTMLElement): ISettingItemTemplate {
DOM.addClass(container, 'setting-item');
@@ -398,7 +350,8 @@ export class SettingsRenderer implements IRenderer {
const titleElement = DOM.append(leftElement, $('.setting-item-title'));
const categoryElement = DOM.append(titleElement, $('span.setting-item-category'));
const labelElement = DOM.append(titleElement, $('span.setting-item-label'));
- const overridesElement = DOM.append(titleElement, $('span.setting-item-overrides'));
+ const isConfiguredElement = DOM.append(titleElement, $('span.setting-item-is-configured-label'));
+ const otherOverridesElement = DOM.append(titleElement, $('span.setting-item-overrides'));
const descriptionElement = DOM.append(leftElement, $('.setting-item-description'));
const expandIndicatorElement = DOM.append(leftElement, $('.expand-indicator'));
@@ -415,7 +368,8 @@ export class SettingsRenderer implements IRenderer {
descriptionElement,
expandIndicatorElement,
valueElement,
- overridesElement
+ isConfiguredElement,
+ otherOverridesElement
};
// Prevent clicks from being handled by list
@@ -433,10 +387,6 @@ export class SettingsRenderer implements IRenderer {
(template).labelElement.textContent = (element).group.title;
return;
}
-
- if (templateId === BUTTON_ROW_ELEMENT_TEMPLATE) {
- return this.renderButtonRowElement(element, template);
- }
}
private elementIsSelected(tree: ITree, element: TreeElement): boolean {
@@ -479,13 +429,16 @@ export class SettingsRenderer implements IRenderer {
this.renderValue(element, isSelected, template);
const resetButton = new Button(template.valueElement);
- resetButton.element.title = localize('resetButtonTitle', "Reset");
+ const resetText = localize('resetButtonTitle', "reset");
+ resetButton.label = resetText;
+ resetButton.element.title = resetText;
resetButton.element.classList.add('setting-reset-button');
resetButton.element.tabIndex = isSelected ? 0 : -1;
attachButtonStyler(resetButton, this.themeService, {
buttonBackground: Color.transparent.toString(),
- buttonHoverBackground: Color.transparent.toString()
+ buttonHoverBackground: Color.transparent.toString(),
+ buttonForeground: editorActiveLinkForeground
});
template.toDispose.push(resetButton.onDidClick(e => {
@@ -493,14 +446,15 @@ export class SettingsRenderer implements IRenderer {
}));
template.toDispose.push(resetButton);
- const alsoConfiguredInLabel = localize('alsoConfiguredIn', "Also modified in:");
- let overridesElementText = element.isConfigured ? 'Modified ' : '';
+ template.isConfiguredElement.textContent = element.isConfigured ? localize('configured', "Modified") : '';
if (element.overriddenScopeList.length) {
- overridesElementText = overridesElementText + `(${alsoConfiguredInLabel} ${element.overriddenScopeList.join(', ')})`;
- }
+ let otherOverridesLabel = element.isConfigured ?
+ localize('alsoConfiguredIn', "Also modified in") :
+ localize('configuredIn', "Modified in");
- template.overridesElement.textContent = overridesElementText;
+ template.otherOverridesElement.textContent = `(${otherOverridesLabel}: ${element.overriddenScopeList.join(', ')})`;
+ }
}
private renderValue(element: ISettingElement, isSelected: boolean, template: ISettingItemTemplate): void {
@@ -568,31 +522,41 @@ export class SettingsRenderer implements IRenderer {
}));
}
- private renderButtonRowElement(element: IButtonElement, template: IButtonRowTemplate): void {
- template.button.label = this.viewState.showAllSettings ?
- localize('showFewerSettings', "Show Fewer Settings") :
- localize('showAllSettings', "Show All Settings");
- }
-
disposeTemplate(tree: ITree, templateId: string, template: IDisposableTemplate): void {
dispose(template.toDispose);
}
}
export class SettingsTreeFilter implements IFilter {
- constructor(private viewState: ISettingsEditorViewState) { }
+ constructor(
+ private viewState: ISettingsEditorViewState,
+ @IConfigurationService private configurationService: IConfigurationService
+ ) { }
isVisible(tree: ITree, element: TreeElement): boolean {
if (this.viewState.showConfiguredOnly && element.type === TreeItemType.setting) {
return element.isConfigured;
}
- if (!this.viewState.showAllSettings && element.type === TreeItemType.groupTitle) {
- return element.index === 0;
+ if (element.type === TreeItemType.groupTitle && this.viewState.showConfiguredOnly) {
+ return this.groupHasConfiguredSetting(element.group);
}
return true;
}
+
+ private groupHasConfiguredSetting(group: ISettingsGroup): boolean {
+ for (let section of group.sections) {
+ for (let setting of section.settings) {
+ const { isConfigured } = inspectSetting(setting.key, this.viewState.settingsTarget, this.configurationService);
+ if (isConfigured) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
export class SettingsTreeController extends WorkbenchTreeController {
@@ -617,10 +581,6 @@ export class SettingsAccessibilityProvider implements IAccessibilityProvider {
return localize('groupRowAriaLabel', "{0}, group", element.group.title);
}
- if (element.type === TreeItemType.buttonRow) {
- return localize('buttonRowAriaLabel', "{0}, button", element.id);
- }
-
return '';
}
}
diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts
index 8ff86fd21877813b037fce21478ce88f9efa4fd7..9c5cbf86e1893a92230120bd8e2e62669aa13cb3 100644
--- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts
+++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts
@@ -192,7 +192,7 @@ const registry = Registry.as(Extensions.WorkbenchActio
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Raw Default Settings', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRawUserSettingsAction, OpenRawUserSettingsAction.ID, OpenRawUserSettingsAction.LABEL), 'Preferences: Open Raw User Settings', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettingsAction, OpenSettingsAction.ID, OpenSettingsAction.LABEL), 'Preferences: Open Settings', category);
-registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_COMMA }), 'Preferences: Open Settings (Experimental)', category);
+registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_COMMA }), 'Preferences: Open Settings (Preview)', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User Settings', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: null }), 'Preferences: Open Keyboard Shortcuts File', category);
diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts
index f74ed02cf9cea712b5969769f0870596433d57ab..efbdc7e94fea1d276336c7903302009fe6c8ae29 100644
--- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts
+++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts
@@ -48,22 +48,48 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
this.config = this._configurationService.getValue(TERMINAL_CONFIG_SECTION);
}
- private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
+ public configFontIsMonospace(): boolean {
+ this._createCharMeasureElementIfNecessary();
+ let fontSize = 15;
+ let fontFamily = this.config.fontFamily || this._configurationService.getValue('editor').fontFamily;
+ let i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
+ let w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
+
+ let invalidBounds = !i_rect.width || !w_rect.width;
+ if (invalidBounds) {
+ // There is no reason to believe the font is not Monospace.
+ return true;
+ }
+
+ return i_rect.width === w_rect.width;
+ }
+
+ private _createCharMeasureElementIfNecessary() {
// Create charMeasureElement if it hasn't been created or if it was orphaned by its parent
if (!this._charMeasureElement || !this._charMeasureElement.parentElement) {
this._charMeasureElement = document.createElement('div');
this.panelContainer.appendChild(this._charMeasureElement);
}
+ }
+ private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect {
const style = this._charMeasureElement.style;
- style.display = 'block';
+ style.display = 'inline-block';
style.fontFamily = fontFamily;
style.fontSize = fontSize + 'px';
style.lineHeight = 'normal';
- this._charMeasureElement.innerText = 'X';
+ this._charMeasureElement.innerText = char;
const rect = this._charMeasureElement.getBoundingClientRect();
style.display = 'none';
+ return rect;
+ }
+
+ private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
+ this._createCharMeasureElementIfNecessary();
+
+ let rect = this._getBoundingRectFor('X', fontFamily, fontSize);
+
// Bounding client rect was invalid, use last font measurement if available.
if (this._lastFontMeasurement && !rect.width && !rect.height) {
return this._lastFontMeasurement;
diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts
index 6bcd8d7dd2ede343362890ec6eac04d1e936c241..a0ffa0e0c216e9593b851e60873fc948f8094dac 100644
--- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts
+++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts
@@ -65,6 +65,7 @@ export class TerminalInstance implements ITerminalInstance {
private _rows: number;
private _windowsShellHelper: WindowsShellHelper;
private _onLineDataListeners: ((lineData: string) => void)[];
+ private _onDataListeners: ((data: string) => void)[];
private _xtermReadyPromise: TPromise;
private _disposables: lifecycle.IDisposable[];
@@ -119,6 +120,7 @@ export class TerminalInstance implements ITerminalInstance {
this._disposables = [];
this._skipTerminalCommands = [];
this._onLineDataListeners = [];
+ this._onDataListeners = [];
this._isExiting = false;
this._hadFocusOnExit = false;
this._isVisible = false;
@@ -279,7 +281,7 @@ export class TerminalInstance implements ITerminalInstance {
this._xterm.winptyCompatInit();
this._xterm.on('linefeed', () => this._onLineFeed());
if (this._processManager) {
- this._processManager.onProcessData(data => this._sendPtyDataToXterm(data));
+ this._processManager.onProcessData(data => this._onProcessData(data));
this._xterm.on('data', data => this._processManager.write(data));
// TODO: How does the cwd work on detached processes?
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager.initialCwd);
@@ -675,13 +677,20 @@ export class TerminalInstance implements ITerminalInstance {
}
}
- private _sendPtyDataToXterm(data: string): void {
+ private _onProcessData(data: string): void {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
if (this._xterm) {
this._xterm.write(data);
}
+ this._onDataListeners.forEach(listener => {
+ try {
+ listener(data);
+ } catch (err) {
+ console.error(`onData listener threw`, err);
+ }
+ });
}
private _onProcessExit(exitCode: number): void {
@@ -775,7 +784,7 @@ export class TerminalInstance implements ITerminalInstance {
if (oldTitle !== this._title) {
this.setTitle(this._title, true);
}
- this._processManager.onProcessData(data => this._sendPtyDataToXterm(data));
+ this._processManager.onProcessData(data => this._onProcessData(data));
// Clean up waitOnExit state
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
@@ -788,7 +797,15 @@ export class TerminalInstance implements ITerminalInstance {
}
public onData(listener: (data: string) => void): lifecycle.IDisposable {
- return this._processManager.onProcessData(data => listener(data));
+ this._onDataListeners.push(listener);
+ return {
+ dispose: () => {
+ const i = this._onDataListeners.indexOf(listener);
+ if (i >= 0) {
+ this._onDataListeners.splice(i, 1);
+ }
+ }
+ };
}
public onLineData(listener: (lineData: string) => void): lifecycle.IDisposable {
diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts
index d07fca022b7e93ce8c2319163c0255c07336d032..7b194643901c5cd54ca153cfb6767be91e1ca955 100644
--- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts
+++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts
@@ -26,6 +26,9 @@ import { PANEL_BACKGROUND, PANEL_BORDER } from 'vs/workbench/common/theme';
import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/parts/terminal/common/terminalColorRegistry';
import { DataTransfers } from 'vs/base/browser/dnd';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
+import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification';
+import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
+import { Severity } from 'vs/editor/editor.api';
export class TerminalPanel extends Panel {
@@ -45,7 +48,8 @@ export class TerminalPanel extends Panel {
@ITerminalService private readonly _terminalService: ITerminalService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IThemeService protected themeService: IThemeService,
- @ITelemetryService telemetryService: ITelemetryService
+ @ITelemetryService telemetryService: ITelemetryService,
+ @INotificationService private readonly _notificationService: INotificationService
) {
super(TERMINAL_PANEL_ID, telemetryService, themeService);
}
@@ -74,6 +78,19 @@ export class TerminalPanel extends Panel {
if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fontFamily')) {
this._updateFont();
}
+
+ if (e.affectsConfiguration('terminal.integrated.fontFamily') || e.affectsConfiguration('editor.fontFamily')) {
+ let configHelper = this._terminalService.configHelper;
+ if (configHelper instanceof TerminalConfigHelper) {
+ if (!configHelper.configFontIsMonospace()) {
+ const choices: IPromptChoice[] = [{
+ label: nls.localize('terminal.useMonospace', "Use 'monospace'"),
+ run: () => this._configurationService.updateValue('terminal.integrated.fontFamily', 'monospace'),
+ }];
+ this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts."), choices);
+ }
+ }
+ }
}));
this._updateFont();
this._updateTheme();
diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts
index cc4cec690c45ca1ed66b6ba137b98ddcfda5d046..27cc89f26cc653bbb099e6fb1795f5e59b7912a3 100644
--- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts
+++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts
@@ -125,4 +125,89 @@ suite('Workbench - TerminalConfigHelper', () => {
configHelper.panelContainer = fixture;
assert.equal(configHelper.getFont().lineHeight, 1, 'editor.lineHeight should be 1 when terminal.integrated.lineHeight not set');
});
+
+ test('TerminalConfigHelper - isMonospace monospace', function () {
+ const configurationService = new TestConfigurationService();
+ configurationService.setUserConfiguration('terminal', {
+ integrated: {
+ fontFamily: 'monospace'
+ }
+ });
+
+ let configHelper = new TerminalConfigHelper(configurationService, null, null, null);
+ configHelper.panelContainer = fixture;
+ assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced');
+ });
+
+ test('TerminalConfigHelper - isMonospace sans-serif', function () {
+ const configurationService = new TestConfigurationService();
+ configurationService.setUserConfiguration('terminal', {
+ integrated: {
+ fontFamily: 'sans-serif'
+ }
+ });
+ let configHelper = new TerminalConfigHelper(configurationService, null, null, null);
+ configHelper.panelContainer = fixture;
+ assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced');
+ });
+
+ test('TerminalConfigHelper - isMonospace serif', function () {
+ const configurationService = new TestConfigurationService();
+ configurationService.setUserConfiguration('terminal', {
+ integrated: {
+ fontFamily: 'serif'
+ }
+ });
+ let configHelper = new TerminalConfigHelper(configurationService, null, null, null);
+ configHelper.panelContainer = fixture;
+ assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced');
+ });
+
+ test('TerminalConfigHelper - isMonospace monospace falls back to editor.fontFamily', function () {
+ const configurationService = new TestConfigurationService();
+ configurationService.setUserConfiguration('editor', {
+ fontFamily: 'monospace'
+ });
+ configurationService.setUserConfiguration('terminal', {
+ integrated: {
+ fontFamily: null
+ }
+ });
+
+ let configHelper = new TerminalConfigHelper(configurationService, null, null, null);
+ configHelper.panelContainer = fixture;
+ assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced');
+ });
+
+ test('TerminalConfigHelper - isMonospace sans-serif falls back to editor.fontFamily', function () {
+ const configurationService = new TestConfigurationService();
+ configurationService.setUserConfiguration('editor', {
+ fontFamily: 'sans-serif'
+ });
+ configurationService.setUserConfiguration('terminal', {
+ integrated: {
+ fontFamily: null
+ }
+ });
+
+ let configHelper = new TerminalConfigHelper(configurationService, null, null, null);
+ configHelper.panelContainer = fixture;
+ assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced');
+ });
+
+ test('TerminalConfigHelper - isMonospace serif falls back to editor.fontFamily', function () {
+ const configurationService = new TestConfigurationService();
+ configurationService.setUserConfiguration('editor', {
+ fontFamily: 'serif'
+ });
+ configurationService.setUserConfiguration('terminal', {
+ integrated: {
+ fontFamily: null
+ }
+ });
+
+ let configHelper = new TerminalConfigHelper(configurationService, null, null, null);
+ configHelper.panelContainer = fixture;
+ assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced');
+ });
});
\ No newline at end of file
diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts
index 930ed10fd4c9ef7359c954f6b7f839e05518107a..6fb507a06561bc4dff6a768223b0dfd63554bb2d 100644
--- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts
+++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts
@@ -98,7 +98,7 @@ export class SettingsEditor2Input extends EditorInput {
}
getName(): string {
- return nls.localize('settingsEditor2InputName', "Settings (Experimental)");
+ return nls.localize('settingsEditor2InputName', "Settings (Preview)");
}
resolve(refresh?: boolean): TPromise {
diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts
index ef0ea1b5654a2c108bb54423aa6ed41eed61551b..d3efcf7e8b05cbb9c05d167c0425b0473b4a6633 100644
--- a/src/vs/workbench/services/search/node/searchService.ts
+++ b/src/vs/workbench/services/search/node/searchService.ts
@@ -121,17 +121,20 @@ export class SearchService implements ISearchService {
// TODO@roblou this is not properly waiting for search-rg to finish registering itself
if (this.searchProvider.length) {
return TPromise.join(this.searchProvider.map(p => searchWithProvider(p)))
- .then(complete => {
- const first: ISearchComplete = complete[0];
- if (!first) {
+ .then(completes => {
+ completes = completes.filter(c => !!c);
+ if (!completes.length) {
return null;
}
return {
- limitHit: first && first.limitHit,
- stats: first.stats,
- results: arrays.flatten(complete.map(c => c.results))
+ limitHit: completes[0] && completes[0].limitHit,
+ stats: completes[0].stats,
+ results: arrays.flatten(completes.map(c => c.results))
};
+ }, errs => {
+ errs = errs.filter(e => !!e);
+ return TPromise.wrapError(errs[0]);
});
} else {
return searchWithProvider(this.diskSearch);
diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts
index 2bc337d218f791770f9084b0880cfa340311d72d..b831fbd49f68027f682dd2fb019546d71a1d829b 100644
--- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts
+++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts
@@ -17,6 +17,7 @@ import * as vscode from 'vscode';
import { dispose } from 'vs/base/common/lifecycle';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { Range } from 'vs/workbench/api/node/extHostTypes';
+import { joinPath } from 'vs/base/common/resources';
let rpcProtocol: TestRPCProtocol;
let extHostSearch: ExtHostSearch;
@@ -53,8 +54,8 @@ class MockMainThreadSearch implements MainThreadSearchShape {
let mockExtfs: Partial;
suite('ExtHostSearch', () => {
- async function registerTestSearchProvider(provider: vscode.SearchProvider): TPromise {
- disposables.push(extHostSearch.registerSearchProvider('file', provider));
+ async function registerTestSearchProvider(provider: vscode.SearchProvider, scheme = 'file'): TPromise {
+ disposables.push(extHostSearch.registerSearchProvider(scheme, provider));
await rpcProtocol.sync();
}
@@ -121,12 +122,8 @@ suite('ExtHostSearch', () => {
const rootFolderA = URI.file('/foo/bar1');
const rootFolderB = URI.file('/foo/bar2');
- // const rootFolderC = URI.file('/foo/bar3');
-
- function makeAbsoluteURI(root: URI, relativePath: string): URI {
- return URI.file(
- path.join(root.fsPath, relativePath));
- }
+ const fancyScheme = 'fancy';
+ const fancySchemeFolderA = URI.from({ scheme: fancyScheme, path: '/project/folder1' });
suite('File:', () => {
@@ -162,9 +159,9 @@ suite('ExtHostSearch', () => {
test('simple results', async () => {
const reportedResults = [
- makeAbsoluteURI(rootFolderA, 'file1.ts'),
- makeAbsoluteURI(rootFolderA, 'file2.ts'),
- makeAbsoluteURI(rootFolderA, 'file3.ts')
+ joinPath(rootFolderA, 'file1.ts'),
+ joinPath(rootFolderA, 'file2.ts'),
+ joinPath(rootFolderA, 'file3.ts')
];
await registerTestSearchProvider({
@@ -383,7 +380,7 @@ suite('ExtHostSearch', () => {
compareURIs(
results,
[
- makeAbsoluteURI(rootFolderA, 'file1.ts')
+ joinPath(rootFolderA, 'file1.ts')
]);
});
@@ -443,20 +440,20 @@ suite('ExtHostSearch', () => {
compareURIs(
results,
[
- makeAbsoluteURI(rootFolderA, 'folder/fileA.scss'),
- makeAbsoluteURI(rootFolderA, 'folder/file2.css'),
+ joinPath(rootFolderA, 'folder/fileA.scss'),
+ joinPath(rootFolderA, 'folder/file2.css'),
- makeAbsoluteURI(rootFolderB, 'fileB.ts'),
- makeAbsoluteURI(rootFolderB, 'fileB.js'),
- makeAbsoluteURI(rootFolderB, 'file3.js'),
+ joinPath(rootFolderB, 'fileB.ts'),
+ joinPath(rootFolderB, 'fileB.js'),
+ joinPath(rootFolderB, 'file3.js'),
]);
});
test('max results = 1', async () => {
const reportedResults = [
- makeAbsoluteURI(rootFolderA, 'file1.ts'),
- makeAbsoluteURI(rootFolderA, 'file2.ts'),
- makeAbsoluteURI(rootFolderA, 'file3.ts'),
+ joinPath(rootFolderA, 'file1.ts'),
+ joinPath(rootFolderA, 'file2.ts'),
+ joinPath(rootFolderA, 'file3.ts'),
];
let wasCanceled = false;
@@ -490,9 +487,9 @@ suite('ExtHostSearch', () => {
test('max results = 2', async () => {
const reportedResults = [
- makeAbsoluteURI(rootFolderA, 'file1.ts'),
- makeAbsoluteURI(rootFolderA, 'file2.ts'),
- makeAbsoluteURI(rootFolderA, 'file3.ts'),
+ joinPath(rootFolderA, 'file1.ts'),
+ joinPath(rootFolderA, 'file2.ts'),
+ joinPath(rootFolderA, 'file3.ts'),
];
let wasCanceled = false;
@@ -567,9 +564,9 @@ suite('ExtHostSearch', () => {
test('respects filePattern', async () => {
const reportedResults = [
- makeAbsoluteURI(rootFolderA, 'file1.ts'),
- makeAbsoluteURI(rootFolderA, 'file2.ts'),
- makeAbsoluteURI(rootFolderA, 'file3.ts'),
+ joinPath(rootFolderA, 'file1.ts'),
+ joinPath(rootFolderA, 'file2.ts'),
+ joinPath(rootFolderA, 'file3.ts'),
];
await registerTestSearchProvider({
@@ -596,6 +593,35 @@ suite('ExtHostSearch', () => {
compareURIs(results, reportedResults.slice(2));
});
+ test('works with non-file schemes', async () => {
+ const reportedResults = [
+ joinPath(fancySchemeFolderA, 'file1.ts'),
+ joinPath(fancySchemeFolderA, 'file2.ts'),
+ joinPath(fancySchemeFolderA, 'file3.ts'),
+
+ ];
+
+ await registerTestSearchProvider({
+ provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable {
+ reportedResults.forEach(r => progress.report(path.basename(r.fsPath)));
+ return TPromise.wrap(null);
+ }
+ }, fancyScheme);
+
+ const query: ISearchQuery = {
+ type: QueryType.File,
+ filePattern: '',
+ folderQueries: [
+ {
+ folder: fancySchemeFolderA
+ }
+ ]
+ };
+
+ const results = await runFileSearch(query);
+ compareURIs(results, reportedResults);
+ });
+
// Mock fs?
// test('Returns result for absolute path', async () => {
// const queriedFile = makeFileResult(rootFolderA, 'file2.ts');
@@ -629,7 +655,6 @@ suite('ExtHostSearch', () => {
}
function makeTextResult(relativePath: string): vscode.TextSearchResult {
- relativePath = relativePath.replace(/\//g, path.sep);
return {
preview: makePreview('foo'),
range: new Range(0, 0, 0, 3),
@@ -653,11 +678,11 @@ suite('ExtHostSearch', () => {
};
}
- function assertResults(actual: IFileMatch[], expected: vscode.TextSearchResult[]) {
+ function assertResults(actual: IFileMatch[], expected: vscode.TextSearchResult[], folder = rootFolderA) {
const actualTextSearchResults: vscode.TextSearchResult[] = [];
for (let fileMatch of actual) {
// Make relative
- const relativePath = fileMatch.resource.fsPath.substr(rootFolderA.fsPath.length + 1);
+ const relativePath = fileMatch.resource.toString().substr(folder.toString().length + 1);
for (let lineMatch of fileMatch.lineMatches) {
for (let [offset, length] of lineMatch.offsetAndLengths) {
actualTextSearchResults.push({
@@ -884,7 +909,7 @@ suite('ExtHostSearch', () => {
test('multiroot sibling clause', async () => {
mockExtfs.readdir = (_path: string, callback: (error: Error, files: string[]) => void) => {
- if (_path === makeAbsoluteURI(rootFolderA, 'folder').fsPath) {
+ if (_path === joinPath(rootFolderA, 'folder').fsPath) {
callback(null, [
'fileA.scss',
'fileA.css',
@@ -1080,5 +1105,31 @@ suite('ExtHostSearch', () => {
assert.equal(results.length, 2);
assert.equal(cancels, 2);
});
+
+ test('works with non-file schemes', async () => {
+ const providedResults: vscode.TextSearchResult[] = [
+ makeTextResult('file1.ts'),
+ makeTextResult('file2.ts'),
+ makeTextResult('file3.ts')
+ ];
+
+ await registerTestSearchProvider({
+ provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable {
+ providedResults.forEach(r => progress.report(r));
+ return TPromise.wrap(null);
+ }
+ }, fancyScheme);
+
+ const query: ISearchQuery = {
+ type: QueryType.Text,
+
+ folderQueries: [
+ { folder: fancySchemeFolderA }
+ ]
+ };
+
+ const results = await runTextSearch(getPattern('foo'), query);
+ assertResults(results, providedResults, fancySchemeFolderA);
+ });
});
});