From e02ccc6d007532927ca176d33bc7fe0513753ec4 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 22 Dec 2016 00:46:41 +0100 Subject: [PATCH 001/140] Fixed #15939 - Added a dedicated property for value used in Windows shell, allowing to provide a custom hotkey. --- build/gulpfile.vscode.win32.js | 1 + build/win32/code.iss | 8 ++++---- product.json | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index cb232748f21..427e4e52538 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -44,6 +44,7 @@ function buildWin32Setup(cb) { NameVersion: product.win32NameVersion, ExeBasename: product.nameShort, RegValueName: product.win32RegValueName, + ShellNameShort: product.win32ShellNameShort, AppMutex: product.win32MutexName, AppId: product.win32AppId, AppUserId: product.win32AppUserModelId, diff --git a/build/win32/code.iss b/build/win32/code.iss index 395406a32b7..538b58087f5 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -765,16 +765,16 @@ Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: s Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin')) -Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufiles Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: addcontextmenufiles -Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders -Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders -Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders diff --git a/product.json b/product.json index 49114baad2b..49f852e2928 100644 --- a/product.json +++ b/product.json @@ -11,6 +11,7 @@ "win32RegValueName": "CodeOSS", "win32AppId": "{{E34003BB-9E10-4501-8C11-BE3FAA83F23F}", "win32AppUserModelId": "Microsoft.CodeOSS", + "win32ShellNameShort": "C&ode - OSS", "darwinBundleIdentifier": "com.visualstudio.code.oss", "reportIssueUrl": "https://github.com/Microsoft/vscode/issues/new", "urlProtocol": "code-oss" From dd0558f23fdfd13c8d672a52e5d4b612c1be05d4 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 8 Feb 2017 15:40:30 -0800 Subject: [PATCH 002/140] copy rendered html to clipboard --- .../controller/input/textAreaWrapper.ts | 5 +- .../browser/controller/keyboardHandler.ts | 2 +- .../common/config/commonEditorConfig.ts | 6 ++ src/vs/editor/common/config/defaultConfig.ts | 1 + .../common/controller/textAreaHandler.ts | 70 ++++++++++++++++++- .../editor/common/controller/textAreaState.ts | 4 +- src/vs/editor/common/editorCommon.ts | 8 +++ .../browser/standaloneRichClipboard.ts | 65 +++++++++++++++++ .../electron-browser/richClipboard.ts | 64 +++++++++++++++++ src/vs/editor/editor.main.ts | 1 + .../test/browser/controller/imeTester.ts | 7 +- .../common/controller/textAreaState.test.ts | 5 ++ src/vs/monaco.d.ts | 5 ++ .../electron-browser/workbench.main.ts | 1 + 14 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts create mode 100644 src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts diff --git a/src/vs/editor/browser/controller/input/textAreaWrapper.ts b/src/vs/editor/browser/controller/input/textAreaWrapper.ts index c9c2efa502f..35c8884eea9 100644 --- a/src/vs/editor/browser/controller/input/textAreaWrapper.ts +++ b/src/vs/editor/browser/controller/input/textAreaWrapper.ts @@ -29,9 +29,12 @@ class ClipboardEventWrapper implements IClipboardEvent { return false; } - public setTextData(text: string): void { + public setTextData(text: string, richText?: string): void { if (this._event.clipboardData) { this._event.clipboardData.setData('text/plain', text); + if (richText !== undefined) { + this._event.clipboardData.setData('text/html', richText); + } this._event.preventDefault(); return; } diff --git a/src/vs/editor/browser/controller/keyboardHandler.ts b/src/vs/editor/browser/controller/keyboardHandler.ts index 89ceea1c90a..77ab8271ab5 100644 --- a/src/vs/editor/browser/controller/keyboardHandler.ts +++ b/src/vs/editor/browser/controller/keyboardHandler.ts @@ -56,7 +56,7 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable { this.contentWidth = 0; this.scrollLeft = 0; - this.textAreaHandler = new TextAreaHandler(browser, this._getStrategy(), this.textArea, this._context.model, () => this.viewHelper.flushAnyAccumulatedEvents()); + this.textAreaHandler = new TextAreaHandler(browser, this._getStrategy(), this.textArea, this._context.model, this._context.configuration.editor, () => this.viewHelper.flushAnyAccumulatedEvents()); this._toDispose = []; this._toDispose.push(this.textAreaHandler.onKeyDown((e) => this.viewController.emitKeyDown(e._actual))); diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index dbb4051faf3..b3744d95233 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -294,6 +294,7 @@ class InternalEditorOptionsHelper { acceptSuggestionOnCommitCharacter: toBoolean(opts.acceptSuggestionOnCommitCharacter), snippetSuggestions: opts.snippetSuggestions, emptySelectionClipboard: opts.emptySelectionClipboard, + richTextClipboard: opts.richTextClipboard, tabCompletion: opts.tabCompletion, wordBasedSuggestions: opts.wordBasedSuggestions, suggestFontSize: opts.suggestFontSize, @@ -704,6 +705,11 @@ const editorConfiguration: IConfigurationNode = { 'default': DefaultConfig.editor.emptySelectionClipboard, 'description': nls.localize('emptySelectionClipboard', "Controls whether copying without a selection copies the current line.") }, + 'editor.richTextClipboard': { + 'type': 'boolean', + 'default': DefaultConfig.editor.richTextClipboard, + 'description': nls.localize('richTextClipboard', "Controls whether copying text with colorization to the clipboard.") + }, 'editor.wordBasedSuggestions': { 'type': 'boolean', 'default': DefaultConfig.editor.wordBasedSuggestions, diff --git a/src/vs/editor/common/config/defaultConfig.ts b/src/vs/editor/common/config/defaultConfig.ts index 2ce761385ae..098d2e8d7d1 100644 --- a/src/vs/editor/common/config/defaultConfig.ts +++ b/src/vs/editor/common/config/defaultConfig.ts @@ -90,6 +90,7 @@ class ConfigClass implements IConfiguration { acceptSuggestionOnCommitCharacter: true, snippetSuggestions: 'bottom', emptySelectionClipboard: true, + richTextClipboard: false, tabCompletion: false, wordBasedSuggestions: true, suggestFontSize: 0, diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 72cdd980e1a..8fdb0e319e6 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -12,7 +12,9 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState } from 'vs/editor/common/controller/textAreaState'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; -import { EndOfLinePreference } from 'vs/editor/common/editorCommon'; +import { EndOfLinePreference, InternalEditorOptions } from 'vs/editor/common/editorCommon'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; const enum ReadFromTextArea { Type, @@ -73,6 +75,7 @@ export class TextAreaHandler extends Disposable { private Browser: IBrowser; private textArea: ITextAreaWrapper; private model: ISimpleModel; + private config: InternalEditorOptions; private flushAnyAccumulatedEvents: () => void; private selection: Range; @@ -91,11 +94,12 @@ export class TextAreaHandler extends Disposable { private _nextCommand: ReadFromTextArea; - constructor(Browser: IBrowser, strategy: TextAreaStrategy, textArea: ITextAreaWrapper, model: ISimpleModel, flushAnyAccumulatedEvents: () => void) { + constructor(Browser: IBrowser, strategy: TextAreaStrategy, textArea: ITextAreaWrapper, model: ISimpleModel, config: InternalEditorOptions, flushAnyAccumulatedEvents: () => void) { super(); this.Browser = Browser; this.textArea = textArea; this.model = model; + this.config = config; this.flushAnyAccumulatedEvents = flushAnyAccumulatedEvents; this.selection = new Range(1, 1, 1, 1); this.selections = [new Range(1, 1, 1, 1)]; @@ -326,7 +330,12 @@ export class TextAreaHandler extends Disposable { private _ensureClipboardGetsEditorSelection(e: IClipboardEvent): void { let whatToCopy = this._getPlainTextToCopy(); if (e.canUseTextData()) { - e.setTextData(whatToCopy); + if (this.config.contribInfo.richTextClipboard) { + let whatRichTextToCopy = this._getHTMLToCopy(); + e.setTextData(whatToCopy, whatRichTextToCopy === undefined ? whatToCopy : whatRichTextToCopy); + } else { + e.setTextData(whatToCopy); + } } else { this.setTextAreaState('copy or cut', this.textAreaState.fromText(whatToCopy), false); } @@ -371,4 +380,59 @@ export class TextAreaHandler extends Disposable { return result.join(newLineCharacter); } } + + private _getHTMLToCopy(): string | undefined { + if (!this.config) { + return undefined; + } + + let selections = this.selections; + + let rules: string[] = []; + let colorMap = TokenizationRegistry.getColorMap(); + for (let i = 1, len = colorMap.length; i < len; i++) { + let color = colorMap[i]; + if (/^(?:[0-9a-fA-F]{3}){1,2}$/.test(color)) { + color = '#' + color; + } + rules[i] = `.mtk${i} { color: ${color}; }`; + } + rules.push('.mtki { font-style: italic; }'); + rules.push('.mtkb { font-weight: bold; }'); + rules.push('.mtku { text-decoration: underline; }'); + + let output = ``; + output += '
'; + + if (selections.length === 1) { + let range: Range = selections[0]; + for (let i = 0, lineCount = range.endLineNumber - range.startLineNumber; i <= lineCount; i++) { + let viewLineRenderingData = this.model.getViewLineRenderingData(range, range.startLineNumber + i); + let lineContent = viewLineRenderingData.content; + let startOffset = i === 0 ? range.startColumn - 1 : 0; + let endOffset = i === lineCount ? range.endColumn - 1 : lineContent.length; + lineContent = viewLineRenderingData.content.substring(startOffset, endOffset); + + let r = renderViewLine(new RenderLineInput( + (this.config.fontInfo.isMonospace && !this.config.viewInfo.disableMonospaceOptimizations), + lineContent, + viewLineRenderingData.mightContainRTL, + 0, + viewLineRenderingData.tokens, + [], + viewLineRenderingData.tabSize, + this.config.fontInfo.spaceWidth, + endOffset, + 'none', + false + )); + + output += `
${r.html}
`; + } + } + + output += '
'; + + return output; + } } diff --git a/src/vs/editor/common/controller/textAreaState.ts b/src/vs/editor/common/controller/textAreaState.ts index b73257f4170..e85c5af694e 100644 --- a/src/vs/editor/common/controller/textAreaState.ts +++ b/src/vs/editor/common/controller/textAreaState.ts @@ -10,10 +10,11 @@ import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/editorCommon'; import { Position } from 'vs/editor/common/core/position'; import { Constants } from 'vs/editor/common/core/uint'; +import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; export interface IClipboardEvent { canUseTextData(): boolean; - setTextData(text: string): void; + setTextData(text: string, richText?: string): void; getTextData(): string; } @@ -56,6 +57,7 @@ export interface ISimpleModel { getValueInRange(range: Range, eol: EndOfLinePreference): string; getModelLineContent(lineNumber: number): string; getLineCount(): number; + getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; coordinatesConverter: { convertViewPositionToModelPosition(viewPosition: Position): Position; diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 7fe393ab654..8e2d1a0d5d5 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -426,6 +426,10 @@ export interface IEditorOptions { * Copying without a selection copies the current line. */ emptySelectionClipboard?: boolean; + /** + * Copying text with colorization to the clipboard + */ + richTextClipboard?: boolean; /** * Enable tab completion. Defaults to 'false' */ @@ -906,6 +910,7 @@ export class EditorContribOptions { readonly acceptSuggestionOnCommitCharacter: boolean; readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'; readonly emptySelectionClipboard: boolean; + readonly richTextClipboard: boolean; readonly tabCompletion: boolean; readonly wordBasedSuggestions: boolean; readonly suggestFontSize: number; @@ -932,6 +937,7 @@ export class EditorContribOptions { acceptSuggestionOnCommitCharacter: boolean; snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'; emptySelectionClipboard: boolean; + richTextClipboard: boolean; tabCompletion: boolean; wordBasedSuggestions: boolean; suggestFontSize: number; @@ -954,6 +960,7 @@ export class EditorContribOptions { this.acceptSuggestionOnCommitCharacter = Boolean(source.acceptSuggestionOnCommitCharacter); this.snippetSuggestions = source.snippetSuggestions; this.emptySelectionClipboard = source.emptySelectionClipboard; + this.richTextClipboard = source.richTextClipboard; this.tabCompletion = source.tabCompletion; this.wordBasedSuggestions = source.wordBasedSuggestions; this.suggestFontSize = source.suggestFontSize; @@ -982,6 +989,7 @@ export class EditorContribOptions { && this.acceptSuggestionOnCommitCharacter === other.acceptSuggestionOnCommitCharacter && this.snippetSuggestions === other.snippetSuggestions && this.emptySelectionClipboard === other.emptySelectionClipboard + && this.richTextClipboard === other.richTextClipboard && this.tabCompletion === other.tabCompletion && this.wordBasedSuggestions === other.wordBasedSuggestions && this.suggestFontSize === other.suggestFontSize diff --git a/src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts b/src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts new file mode 100644 index 00000000000..9703d94d8ee --- /dev/null +++ b/src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as editorCommon from 'vs/editor/common/editorCommon'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; +import { IModeService } from 'vs/editor/common/services/modeService'; + +@editorContribution +class CopyRichTextController extends Disposable implements editorCommon.IEditorContribution { + + private static ID = 'editor.contrib.copyRichText'; + + public static get(editor: editorCommon.ICommonCodeEditor): CopyRichTextController { + return editor.getContribution(CopyRichTextController.ID); + } + + private _editor: ICodeEditor; + private _modeService: IModeService; + private _handler: (event: any) => void; + + constructor( + editor: ICodeEditor, + @IModeService modeService: IModeService, + ) { + super(); + this._editor = editor; + this._modeService = modeService; + this._handler = (e: ClipboardEvent) => { + if (e.target instanceof HTMLElement) { + const target = e.target; + if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) { + let richText = e.clipboardData.getData('text/html'); + if (richText) { + let themeId = this._editor.getConfiguration().viewInfo.theme; + let color: string; + if (/vs-dark($| )/.test(themeId)) { + color = 'color: #BBB;background: #1E1E1E;'; + } if (/vs($| )/.test(themeId)) { + color = 'color: #333;background: #fffffe;'; + } else { + color = 'color: #fff;background: #000;'; + } + + e.clipboardData.setData('text/html', `\n${richText}`); + } + } + } + }; + + document.addEventListener('copy', this._handler); + } + + public getId(): string { + return CopyRichTextController.ID; + } + + public dispose(): void { + document.removeEventListener('copy', this._handler); + super.dispose(); + } +} \ No newline at end of file diff --git a/src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts b/src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts new file mode 100644 index 00000000000..84371943c21 --- /dev/null +++ b/src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as editorCommon from 'vs/editor/common/editorCommon'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; + +@editorContribution +class CopyRichTextController extends Disposable implements editorCommon.IEditorContribution { + + private static ID = 'editor.contrib.copyRichText'; + + public static get(editor: editorCommon.ICommonCodeEditor): CopyRichTextController { + return editor.getContribution(CopyRichTextController.ID); + } + + private _editor: ICodeEditor; + private _themeService: IThemeService; + private _modeService: IModeService; + private _handler: (event: any) => void; + + constructor( + editor: ICodeEditor, + @IModeService modeService: IModeService, + @IThemeService themeService: IThemeService + ) { + super(); + this._editor = editor; + this._themeService = themeService; + this._modeService = modeService; + this._handler = (e: ClipboardEvent) => { + if (e.target instanceof HTMLElement) { + const target = e.target; + if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) { + let richText = e.clipboardData.getData('text/html'); + if (richText) { + let theme = this._themeService.getColorTheme(); + let globalSettings = theme.settings.filter(s => !s.scope); + if (globalSettings.length > 0) { + let backgroundColor = globalSettings[0].settings.background; + e.clipboardData.setData('text/html', `\n${richText}`); + } + } + } + } + }; + + window.document.addEventListener('copy', this._handler); + } + + public getId(): string { + return CopyRichTextController.ID; + } + + public dispose(): void { + window.document.removeEventListener('copy', this._handler); + super.dispose(); + } +} \ No newline at end of file diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index a0cf669373a..2016a6effb3 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -10,6 +10,7 @@ import 'vs/editor/contrib/quickOpen/browser/quickOutline'; import 'vs/editor/contrib/quickOpen/browser/gotoLine'; import 'vs/editor/contrib/quickOpen/browser/quickCommand'; import 'vs/editor/contrib/inspectTokens/browser/inspectTokens'; +import 'vs/editor/contrib/clipboard/browser/standaloneRichClipboard'; import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase'; import { createMonacoEditorAPI } from 'vs/editor/browser/standalone/standaloneEditor'; diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 6872d63cf9a..a3efd6f5254 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -11,6 +11,7 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextAreaWrapper } from 'vs/editor/browser/controller/input/textAreaWrapper'; import { Position } from 'vs/editor/common/core/position'; +import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; // To run this test, open imeTester.html @@ -53,6 +54,10 @@ class SingleLineTestModel implements ISimpleModel { getLineCount(): number { return 1; } + + public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { + return null; + } } class TestView { @@ -96,7 +101,7 @@ function doCreateTest(strategy: TextAreaStrategy, description: string, inputStr: let model = new SingleLineTestModel('some text'); - let handler = new TextAreaHandler(browser, strategy, textAreaWrapper, model, () => { }); + let handler = new TextAreaHandler(browser, strategy, textAreaWrapper, model, null, () => { }); input.onfocus = () => { handler.setHasFocus(true); diff --git a/src/vs/editor/test/common/controller/textAreaState.test.ts b/src/vs/editor/test/common/controller/textAreaState.test.ts index 959974b9e33..8064bd94b45 100644 --- a/src/vs/editor/test/common/controller/textAreaState.test.ts +++ b/src/vs/editor/test/common/controller/textAreaState.test.ts @@ -10,6 +10,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/editorCommon'; import { MockTextAreaWrapper } from 'vs/editor/test/common/mocks/mockTextAreaWrapper'; +import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; suite('TextAreaState', () => { @@ -472,4 +473,8 @@ class SimpleModel implements ISimpleModel { public getLineCount(): number { return this._lines.length; } + + public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { + return null; + } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 832a15a0f11..55ce504574e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1348,6 +1348,10 @@ declare module monaco.editor { * Copying without a selection copies the current line. */ emptySelectionClipboard?: boolean; + /** + * Copying text with colorization to the clipboard + */ + richTextClipboard?: boolean; /** * Enable tab completion. Defaults to 'false' */ @@ -1556,6 +1560,7 @@ declare module monaco.editor { readonly acceptSuggestionOnCommitCharacter: boolean; readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'; readonly emptySelectionClipboard: boolean; + readonly richTextClipboard: boolean; readonly tabCompletion: boolean; readonly wordBasedSuggestions: boolean; readonly suggestFontSize: number; diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index 891f770bbdb..e7f009ba196 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -12,6 +12,7 @@ import 'vs/base/common/errors'; // Editor import 'vs/editor/contrib/accessibility/browser/accessibility'; import 'vs/editor/contrib/defineKeybinding/browser/defineKeybinding'; +import 'vs/editor/contrib/clipboard/electron-browser/richClipboard'; import 'vs/editor/contrib/inspectTMScopes/electron-browser/inspectTMScopes'; import 'vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard'; import 'vs/editor/browser/editor.all'; From 0a893ff46ea5a55dc8be5778e562c8be66bb4b24 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 9 Feb 2017 15:14:48 -0800 Subject: [PATCH 003/140] use inline styles --- .../browser/controller/keyboardHandler.ts | 2 +- .../common/controller/textAreaHandler.ts | 102 +---------------- .../editor/common/controller/textAreaState.ts | 4 +- .../common/modes/textToHtmlTokenizer.ts | 104 ++++++++++++++++++ src/vs/editor/common/viewModel/viewModel.ts | 3 + .../editor/common/viewModel/viewModelImpl.ts | 95 ++++++++++++++++ .../browser/standaloneRichClipboard.ts | 65 ----------- .../electron-browser/richClipboard.ts | 64 ----------- src/vs/editor/editor.main.ts | 1 - .../test/browser/controller/imeTester.ts | 13 ++- .../common/controller/textAreaState.test.ts | 9 +- .../electron-browser/workbench.main.ts | 1 - 12 files changed, 224 insertions(+), 239 deletions(-) delete mode 100644 src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts delete mode 100644 src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts diff --git a/src/vs/editor/browser/controller/keyboardHandler.ts b/src/vs/editor/browser/controller/keyboardHandler.ts index 77ab8271ab5..89ceea1c90a 100644 --- a/src/vs/editor/browser/controller/keyboardHandler.ts +++ b/src/vs/editor/browser/controller/keyboardHandler.ts @@ -56,7 +56,7 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable { this.contentWidth = 0; this.scrollLeft = 0; - this.textAreaHandler = new TextAreaHandler(browser, this._getStrategy(), this.textArea, this._context.model, this._context.configuration.editor, () => this.viewHelper.flushAnyAccumulatedEvents()); + this.textAreaHandler = new TextAreaHandler(browser, this._getStrategy(), this.textArea, this._context.model, () => this.viewHelper.flushAnyAccumulatedEvents()); this._toDispose = []; this._toDispose.push(this.textAreaHandler.onKeyDown((e) => this.viewController.emitKeyDown(e._actual))); diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 8fdb0e319e6..70835df946b 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -11,10 +11,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState } from 'vs/editor/common/controller/textAreaState'; import { Range } from 'vs/editor/common/core/range'; -import { Position } from 'vs/editor/common/core/position'; -import { EndOfLinePreference, InternalEditorOptions } from 'vs/editor/common/editorCommon'; -import { TokenizationRegistry } from 'vs/editor/common/modes'; -import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; const enum ReadFromTextArea { Type, @@ -75,7 +71,6 @@ export class TextAreaHandler extends Disposable { private Browser: IBrowser; private textArea: ITextAreaWrapper; private model: ISimpleModel; - private config: InternalEditorOptions; private flushAnyAccumulatedEvents: () => void; private selection: Range; @@ -94,12 +89,11 @@ export class TextAreaHandler extends Disposable { private _nextCommand: ReadFromTextArea; - constructor(Browser: IBrowser, strategy: TextAreaStrategy, textArea: ITextAreaWrapper, model: ISimpleModel, config: InternalEditorOptions, flushAnyAccumulatedEvents: () => void) { + constructor(Browser: IBrowser, strategy: TextAreaStrategy, textArea: ITextAreaWrapper, model: ISimpleModel, flushAnyAccumulatedEvents: () => void) { super(); this.Browser = Browser; this.textArea = textArea; this.model = model; - this.config = config; this.flushAnyAccumulatedEvents = flushAnyAccumulatedEvents; this.selection = new Range(1, 1, 1, 1); this.selections = [new Range(1, 1, 1, 1)]; @@ -328,14 +322,10 @@ export class TextAreaHandler extends Disposable { // ------------- Clipboard operations private _ensureClipboardGetsEditorSelection(e: IClipboardEvent): void { - let whatToCopy = this._getPlainTextToCopy(); + let whatToCopy = this.model.getPlainTextToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); if (e.canUseTextData()) { - if (this.config.contribInfo.richTextClipboard) { - let whatRichTextToCopy = this._getHTMLToCopy(); - e.setTextData(whatToCopy, whatRichTextToCopy === undefined ? whatToCopy : whatRichTextToCopy); - } else { - e.setTextData(whatToCopy); - } + let whatHTMLToCopy = this.model.getHTMLToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); + e.setTextData(whatToCopy, whatHTMLToCopy); } else { this.setTextAreaState('copy or cut', this.textAreaState.fromText(whatToCopy), false); } @@ -353,86 +343,4 @@ export class TextAreaHandler extends Disposable { this.lastCopiedValueIsFromEmptySelection = (selections.length === 1 && selections[0].isEmpty()); } } - - private _getPlainTextToCopy(): string { - let newLineCharacter = this.model.getEOL(); - let selections = this.selections; - - if (selections.length === 1) { - let range: Range = selections[0]; - if (range.isEmpty()) { - if (this.Browser.enableEmptySelectionClipboard) { - let modelLineNumber = this.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; - return this.model.getModelLineContent(modelLineNumber) + newLineCharacter; - } else { - return ''; - } - } - - return this.model.getValueInRange(range, EndOfLinePreference.TextDefined); - } else { - selections = selections.slice(0).sort(Range.compareRangesUsingStarts); - let result: string[] = []; - for (let i = 0; i < selections.length; i++) { - result.push(this.model.getValueInRange(selections[i], EndOfLinePreference.TextDefined)); - } - - return result.join(newLineCharacter); - } - } - - private _getHTMLToCopy(): string | undefined { - if (!this.config) { - return undefined; - } - - let selections = this.selections; - - let rules: string[] = []; - let colorMap = TokenizationRegistry.getColorMap(); - for (let i = 1, len = colorMap.length; i < len; i++) { - let color = colorMap[i]; - if (/^(?:[0-9a-fA-F]{3}){1,2}$/.test(color)) { - color = '#' + color; - } - rules[i] = `.mtk${i} { color: ${color}; }`; - } - rules.push('.mtki { font-style: italic; }'); - rules.push('.mtkb { font-weight: bold; }'); - rules.push('.mtku { text-decoration: underline; }'); - - let output = ``; - output += '
'; - - if (selections.length === 1) { - let range: Range = selections[0]; - for (let i = 0, lineCount = range.endLineNumber - range.startLineNumber; i <= lineCount; i++) { - let viewLineRenderingData = this.model.getViewLineRenderingData(range, range.startLineNumber + i); - let lineContent = viewLineRenderingData.content; - let startOffset = i === 0 ? range.startColumn - 1 : 0; - let endOffset = i === lineCount ? range.endColumn - 1 : lineContent.length; - lineContent = viewLineRenderingData.content.substring(startOffset, endOffset); - - let r = renderViewLine(new RenderLineInput( - (this.config.fontInfo.isMonospace && !this.config.viewInfo.disableMonospaceOptimizations), - lineContent, - viewLineRenderingData.mightContainRTL, - 0, - viewLineRenderingData.tokens, - [], - viewLineRenderingData.tabSize, - this.config.fontInfo.spaceWidth, - endOffset, - 'none', - false - )); - - output += `
${r.html}
`; - } - } - - output += '
'; - - return output; - } -} +} \ No newline at end of file diff --git a/src/vs/editor/common/controller/textAreaState.ts b/src/vs/editor/common/controller/textAreaState.ts index e85c5af694e..568ba1e959c 100644 --- a/src/vs/editor/common/controller/textAreaState.ts +++ b/src/vs/editor/common/controller/textAreaState.ts @@ -10,7 +10,6 @@ import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/editorCommon'; import { Position } from 'vs/editor/common/core/position'; import { Constants } from 'vs/editor/common/core/uint'; -import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; export interface IClipboardEvent { canUseTextData(): boolean; @@ -57,7 +56,8 @@ export interface ISimpleModel { getValueInRange(range: Range, eol: EndOfLinePreference): string; getModelLineContent(lineNumber: number): string; getLineCount(): number; - getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; + getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; + getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; coordinatesConverter: { convertViewPositionToModelPosition(viewPosition: Position): Position; diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index e81acfc9802..312da79fe4b 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -8,11 +8,115 @@ import * as strings from 'vs/base/common/strings'; import { IState, ITokenizationSupport, TokenizationRegistry, LanguageId } from 'vs/editor/common/modes'; import { NULL_STATE, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; +import { CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { CharCode } from 'vs/base/common/charCode'; +import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; export function tokenizeToString(text: string, languageId: string): string { return _tokenizeToString(text, _getSafeTokenizationSupport(languageId)); } +export function tokenizeLineToHTML(text: string, viewLineTokens: ViewLineToken[], rules: { [key: string]: string }, options: { startOffset: number, endOffset: number, tabSize: number, containsRTL: boolean }): string { + let tabSize = options.tabSize; + let containsRTL = options.containsRTL; + + let result = `
`; + const characterMapping = new CharacterMapping(text.length + 1, viewLineTokens.length); + + let charIndex = options.startOffset; + let tabsCharDelta = 0; + let charOffsetInPart = 0; + + for (let tokenIndex = 0, lenJ = viewLineTokens.length; tokenIndex < lenJ; tokenIndex++) { + const token = viewLineTokens[tokenIndex]; + const tokenEndIndex = token.endIndex; + + if (token.endIndex < options.startOffset) { + continue; + } + + const tokenType = token.type; + let partContentCnt = 0; + let partContent = ''; + + for (; charIndex < tokenEndIndex && charIndex < options.endOffset; charIndex++) { + characterMapping.setPartData(charIndex, tokenIndex, charOffsetInPart); + const charCode = text.charCodeAt(charIndex); + + switch (charCode) { + case CharCode.Tab: + let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; + tabsCharDelta += insertSpacesCount - 1; + charOffsetInPart += insertSpacesCount - 1; + while (insertSpacesCount > 0) { + partContent += ' '; + partContentCnt++; + insertSpacesCount--; + } + break; + + case CharCode.Space: + partContent += ' '; + partContentCnt++; + break; + + case CharCode.LessThan: + partContent += '<'; + partContentCnt++; + break; + + case CharCode.GreaterThan: + partContent += '>'; + partContentCnt++; + break; + + case CharCode.Ampersand: + partContent += '&'; + partContentCnt++; + break; + + case CharCode.Null: + partContent += '�'; + partContentCnt++; + break; + + case CharCode.UTF8_BOM: + case CharCode.LINE_SEPARATOR_2028: + partContent += '\ufffd'; + partContentCnt++; + break; + + case CharCode.CarriageReturn: + // zero width space, because carriage return would introduce a line break + partContent += '​'; + partContentCnt++; + break; + + default: + partContent += String.fromCharCode(charCode); + partContentCnt++; + } + + charOffsetInPart++; + } + + characterMapping.setPartLength(tokenIndex, partContentCnt); + let style = tokenType.split(' ').map(type => rules[type]).join(''); + if (containsRTL) { + result += `${partContent}`; + } else { + result += `${partContent}`; + } + + if (token.endIndex > options.endOffset) { + break; + } + } + + result += `
`; + return result; +} + function _getSafeTokenizationSupport(languageId: string): ITokenizationSupport { let tokenizationSupport = TokenizationRegistry.get(languageId); if (tokenizationSupport) { diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index cdff538797a..e1e6f8f5317 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -87,6 +87,9 @@ export interface IViewModel extends IEventEmitter { getModelLineContent(modelLineNumber: number): string; getModelLineMaxColumn(modelLineNumber: number): number; validateModelPosition(modelPosition: IPosition): Position; + + getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; + getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; } export class ViewLineRenderingData { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 4087ebf89ef..24c55b3269f 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -11,6 +11,8 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ViewModelCursors } from 'vs/editor/common/viewModel/viewModelCursors'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; @@ -558,4 +560,97 @@ export class ViewModel extends EventEmitter implements IViewModel { public validateModelPosition(position: editorCommon.IPosition): Position { return this.model.validatePosition(position); } + + public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + let newLineCharacter = this.getEOL(); + + if (ranges.length === 1) { + let range: Range = ranges[0]; + if (range.isEmpty()) { + if (enableEmptySelectionClipboard) { + let modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; + return this.getModelLineContent(modelLineNumber) + newLineCharacter; + } else { + return ''; + } + } + + return this.getValueInRange(range, editorCommon.EndOfLinePreference.TextDefined); + } else { + ranges = ranges.slice(0).sort(Range.compareRangesUsingStarts); + let result: string[] = []; + for (let i = 0; i < ranges.length; i++) { + result.push(this.getValueInRange(ranges[i], editorCommon.EndOfLinePreference.TextDefined)); + } + + return result.join(newLineCharacter); + } + } + + public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + let rules: { [key: string]: string } = {}; + let colorMap = TokenizationRegistry.getColorMap(); + for (let i = 1, len = colorMap.length; i < len; i++) { + let color = colorMap[i]; + if (/^(?:[0-9a-fA-F]{3}){1,2}$/.test(color)) { + color = '#' + color; + } + rules[`mtk${i}`] = `color: ${color};`; + } + rules['mtki'] = 'font-style: italic;'; + rules['mtkb'] = 'font-weight: bold;'; + rules['mtku'] = 'text-decoration: underline;'; + + let defaultForegroundColor = /^(?:[0-9a-fA-F]{3}){1,2}$/.test(colorMap[1]) ? '#' + colorMap[1] : colorMap[1]; + let defaultBackgroundColor = /^(?:[0-9a-fA-F]{3}){1,2}$/.test(colorMap[2]) ? '#' + colorMap[2] : colorMap[2]; + + let output = `
`; + + if (ranges.length === 1) { + let range: Range = ranges[0]; + + if (range.isEmpty()) { + if (enableEmptySelectionClipboard) { + let modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; + let viewLineStart = new Position(range.startLineNumber, 1); + let viewLineEnd = new Position(range.startLineNumber, this.getLineMaxColumn(range.startLineNumber)); + let startOffset = this.coordinatesConverter.convertViewPositionToModelPosition(viewLineStart).column - 1; + let endOffset = this.coordinatesConverter.convertViewPositionToModelPosition(viewLineEnd).column - 1; + let viewLineRenderingData = this.getViewLineRenderingData(new Range(viewLineStart.lineNumber, viewLineStart.column, viewLineEnd.lineNumber, viewLineEnd.column), modelLineNumber); + let html = tokenizeLineToHTML(this.getModelLineContent(modelLineNumber), + viewLineRenderingData.tokens, + rules, + { + startOffset: startOffset, + endOffset: endOffset, + tabSize: this.getTabSize(), + containsRTL: this.model.mightContainRTL() + }); + output += `${html}`; + } else { + return ''; + } + } else { + for (let i = 0, lineCount = range.endLineNumber - range.startLineNumber; i <= lineCount; i++) { + let viewLineRenderingData = this.getViewLineRenderingData(range, range.startLineNumber + i); + let lineContent = viewLineRenderingData.content; + let startOffset = i === 0 ? range.startColumn - 1 : 0; + let endOffset = i === lineCount ? range.endColumn - 1 : lineContent.length; + + let html = tokenizeLineToHTML(lineContent, viewLineRenderingData.tokens, rules, + { + startOffset: startOffset, + endOffset: endOffset, + tabSize: this.getTabSize(), + containsRTL: this.model.mightContainRTL() + }); + output += `${html}`; + } + } + } + + output += '
'; + + return output; + } } diff --git a/src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts b/src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts deleted file mode 100644 index 9703d94d8ee..00000000000 --- a/src/vs/editor/contrib/clipboard/browser/standaloneRichClipboard.ts +++ /dev/null @@ -1,65 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; -import { IModeService } from 'vs/editor/common/services/modeService'; - -@editorContribution -class CopyRichTextController extends Disposable implements editorCommon.IEditorContribution { - - private static ID = 'editor.contrib.copyRichText'; - - public static get(editor: editorCommon.ICommonCodeEditor): CopyRichTextController { - return editor.getContribution(CopyRichTextController.ID); - } - - private _editor: ICodeEditor; - private _modeService: IModeService; - private _handler: (event: any) => void; - - constructor( - editor: ICodeEditor, - @IModeService modeService: IModeService, - ) { - super(); - this._editor = editor; - this._modeService = modeService; - this._handler = (e: ClipboardEvent) => { - if (e.target instanceof HTMLElement) { - const target = e.target; - if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) { - let richText = e.clipboardData.getData('text/html'); - if (richText) { - let themeId = this._editor.getConfiguration().viewInfo.theme; - let color: string; - if (/vs-dark($| )/.test(themeId)) { - color = 'color: #BBB;background: #1E1E1E;'; - } if (/vs($| )/.test(themeId)) { - color = 'color: #333;background: #fffffe;'; - } else { - color = 'color: #fff;background: #000;'; - } - - e.clipboardData.setData('text/html', `\n${richText}`); - } - } - } - }; - - document.addEventListener('copy', this._handler); - } - - public getId(): string { - return CopyRichTextController.ID; - } - - public dispose(): void { - document.removeEventListener('copy', this._handler); - super.dispose(); - } -} \ No newline at end of file diff --git a/src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts b/src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts deleted file mode 100644 index 84371943c21..00000000000 --- a/src/vs/editor/contrib/clipboard/electron-browser/richClipboard.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; - -@editorContribution -class CopyRichTextController extends Disposable implements editorCommon.IEditorContribution { - - private static ID = 'editor.contrib.copyRichText'; - - public static get(editor: editorCommon.ICommonCodeEditor): CopyRichTextController { - return editor.getContribution(CopyRichTextController.ID); - } - - private _editor: ICodeEditor; - private _themeService: IThemeService; - private _modeService: IModeService; - private _handler: (event: any) => void; - - constructor( - editor: ICodeEditor, - @IModeService modeService: IModeService, - @IThemeService themeService: IThemeService - ) { - super(); - this._editor = editor; - this._themeService = themeService; - this._modeService = modeService; - this._handler = (e: ClipboardEvent) => { - if (e.target instanceof HTMLElement) { - const target = e.target; - if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) { - let richText = e.clipboardData.getData('text/html'); - if (richText) { - let theme = this._themeService.getColorTheme(); - let globalSettings = theme.settings.filter(s => !s.scope); - if (globalSettings.length > 0) { - let backgroundColor = globalSettings[0].settings.background; - e.clipboardData.setData('text/html', `\n${richText}`); - } - } - } - } - }; - - window.document.addEventListener('copy', this._handler); - } - - public getId(): string { - return CopyRichTextController.ID; - } - - public dispose(): void { - window.document.removeEventListener('copy', this._handler); - super.dispose(); - } -} \ No newline at end of file diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index 2016a6effb3..a0cf669373a 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -10,7 +10,6 @@ import 'vs/editor/contrib/quickOpen/browser/quickOutline'; import 'vs/editor/contrib/quickOpen/browser/gotoLine'; import 'vs/editor/contrib/quickOpen/browser/quickCommand'; import 'vs/editor/contrib/inspectTokens/browser/inspectTokens'; -import 'vs/editor/contrib/clipboard/browser/standaloneRichClipboard'; import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase'; import { createMonacoEditorAPI } from 'vs/editor/browser/standalone/standaloneEditor'; diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index a3efd6f5254..02ee516811b 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -11,7 +11,6 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextAreaWrapper } from 'vs/editor/browser/controller/input/textAreaWrapper'; import { Position } from 'vs/editor/common/core/position'; -import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; // To run this test, open imeTester.html @@ -55,8 +54,12 @@ class SingleLineTestModel implements ISimpleModel { return 1; } - public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { - return null; + public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; + } + + public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; } } @@ -101,7 +104,7 @@ function doCreateTest(strategy: TextAreaStrategy, description: string, inputStr: let model = new SingleLineTestModel('some text'); - let handler = new TextAreaHandler(browser, strategy, textAreaWrapper, model, null, () => { }); + let handler = new TextAreaHandler(browser, strategy, textAreaWrapper, model, () => { }); input.onfocus = () => { handler.setHasFocus(true); @@ -185,4 +188,4 @@ const TESTS = [ TESTS.forEach((t) => { document.body.appendChild(doCreateTest(TextAreaStrategy.NVDA, t.description, t.in, t.out)); document.body.appendChild(doCreateTest(TextAreaStrategy.IENarrator, t.description, t.in, t.out)); -}); +}); \ No newline at end of file diff --git a/src/vs/editor/test/common/controller/textAreaState.test.ts b/src/vs/editor/test/common/controller/textAreaState.test.ts index 8064bd94b45..6da10786e1f 100644 --- a/src/vs/editor/test/common/controller/textAreaState.test.ts +++ b/src/vs/editor/test/common/controller/textAreaState.test.ts @@ -10,7 +10,6 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/editorCommon'; import { MockTextAreaWrapper } from 'vs/editor/test/common/mocks/mockTextAreaWrapper'; -import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; suite('TextAreaState', () => { @@ -474,7 +473,11 @@ class SimpleModel implements ISimpleModel { return this._lines.length; } - public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { - return null; + public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; + } + + public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; } } diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index e7f009ba196..891f770bbdb 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -12,7 +12,6 @@ import 'vs/base/common/errors'; // Editor import 'vs/editor/contrib/accessibility/browser/accessibility'; import 'vs/editor/contrib/defineKeybinding/browser/defineKeybinding'; -import 'vs/editor/contrib/clipboard/electron-browser/richClipboard'; import 'vs/editor/contrib/inspectTMScopes/electron-browser/inspectTMScopes'; import 'vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard'; import 'vs/editor/browser/editor.all'; From 8bcd40f152a99e64170172349584595819bef5fc Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Fri, 10 Feb 2017 17:29:42 +0100 Subject: [PATCH 004/140] First version to support keybindings for tasks --- .../taskConfiguration.ts} | 65 ++++++++++++++- .../parts/tasks/common/taskSystem.ts | 22 +++++ .../electron-browser/task.contribution.ts | 82 +++++++++++++------ .../parts/tasks/node/processRunnerDetector.ts | 26 +++--- .../parts/tasks/node/processRunnerSystem.ts | 8 +- .../tasks/test/node/configuration.test.ts | 2 +- 6 files changed, 159 insertions(+), 46 deletions(-) rename src/vs/workbench/parts/tasks/{node/processRunnerConfiguration.ts => common/taskConfiguration.ts} (94%) diff --git a/src/vs/workbench/parts/tasks/node/processRunnerConfiguration.ts b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts similarity index 94% rename from src/vs/workbench/parts/tasks/node/processRunnerConfiguration.ts rename to src/vs/workbench/parts/tasks/common/taskConfiguration.ts index cc641c41945..f99b4e2f51b 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerConfiguration.ts +++ b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts @@ -14,8 +14,12 @@ import * as UUID from 'vs/base/common/uuid'; import { Config as ProcessConfig } from 'vs/base/common/processes'; import { ValidationStatus, ValidationState, ILogger } from 'vs/base/common/parsers'; -import { NamedProblemMatcher, ProblemMatcher, ProblemMatcherParser, Config as ProblemMatcherConfig, registry as ProblemMatcherRegistry, isNamedProblemMatcher } from 'vs/platform/markers/common/problemMatcher'; -import * as TaskSystem from 'vs/workbench/parts/tasks/common/taskSystem'; +import { + NamedProblemMatcher, ProblemMatcher, ProblemMatcherParser, Config as ProblemMatcherConfig, + registry as ProblemMatcherRegistry, isNamedProblemMatcher +} from 'vs/platform/markers/common/problemMatcher'; + +import * as TaskSystem from './taskSystem'; /** * Defines the problem handling strategy @@ -55,6 +59,24 @@ export interface PlatformTaskDescription { args?: string[]; } +export interface CommandBinding { + /** + * The command Id the task is bound to. + */ + commandId?: string; + + /** + * The title to use + */ + title?: string; + + /** + * An optional category + */ + category?: string; +} + + /** * The description of a task. */ @@ -122,6 +144,11 @@ export interface TaskDescription extends PlatformTaskDescription { */ suppressTaskName?: boolean; + /** + * The command this task is bound to. + */ + bindTo?: CommandBinding; + /** * The problem matcher(s) to use to capture problems in the tasks * output. @@ -623,6 +650,37 @@ namespace ProblemMatcherConverter { } } +namespace CommandBinding { + export function isEmpty(value: TaskSystem.CommandBinding): boolean { + return !value || value.commandId === void 0 && value.title === void 0 && value.category === void 0; + } + + export function from(this: void, binding: CommandBinding, context: ParseContext): TaskSystem.CommandBinding { + if (!binding) { + return undefined; + } + + if (!Types.isString(binding.commandId)) { + context.validationStatus.state = ValidationState.Warning; + context.logger.log(nls.localize('noCommandId', 'Warning: a command binding must defined a commandId. Ignoring binding.')); + return undefined; + } + if (!Types.isString(binding.title)) { + context.validationStatus.state = ValidationState.Warning; + context.logger.log(nls.localize('noTitle', 'Warning: a command binding must defined a title. Ignoring binding.')); + return undefined; + } + let result: TaskSystem.CommandBinding = { + commandId: binding.commandId, + title: binding.title + }; + if (Types.isString(binding.category)) { + result.category = binding.category; + } + return result; + } +} + namespace TaskDescription { export interface TaskConfiguration { @@ -682,6 +740,9 @@ namespace TaskDescription { task.suppressTaskName = !!externalTask.suppressTaskName; } + if (externalTask.bindTo) { + task.bindTo = CommandBinding.from(externalTask.bindTo, context); + } if (problemMatchers) { task.problemMatchers = problemMatchers; } diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index 968878de578..60f6b31f02d 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -136,6 +136,23 @@ export interface CommandConfiguration { echo?: boolean; } +export interface CommandBinding { + /** + * The command Id the task is bound to. + */ + commandId: string; + + /** + * The title to use + */ + title: string; + + /** + * An optional category + */ + category?: string; +} + /** * A task description */ @@ -183,6 +200,11 @@ export interface TaskDescription { */ showOutput: ShowOutput; + /** + * The command this task is bound to. + */ + bindTo?: CommandBinding; + /** * The problem watchers to use for this task */ diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 78b03e8f017..a9dfddffd64 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -28,7 +28,7 @@ import * as strings from 'vs/base/common/strings'; import { Registry } from 'vs/platform/platform'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IEditor } from 'vs/platform/editor/common/editor'; import { IMessageService } from 'vs/platform/message/common/message'; @@ -37,6 +37,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -65,7 +66,7 @@ import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskErr import { ITaskService, TaskServiceEvents } from 'vs/workbench/parts/tasks/common/taskService'; import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; -import * as FileConfig from 'vs/workbench/parts/tasks/node/processRunnerConfiguration'; +import * as TaskConfig from 'vs/workbench/parts/tasks/common/taskConfiguration'; import { ProcessRunnerSystem } from 'vs/workbench/parts/tasks/node/processRunnerSystem'; import { TerminalTaskSystem } from './terminalTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunnerDetector'; @@ -73,6 +74,7 @@ import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunn import { IEnvironmentService } from 'vs/platform/environment/common/environment'; let $ = Builder.$; +let tasksCategory = nls.localize('tasksCategory', "Tasks"); class AbstractTaskAction extends Action { @@ -170,6 +172,20 @@ class CleanAction extends AbstractTaskAction { } } +class CommandAction extends AbstractTaskAction { + constructor(id: string, label: string, private taskId: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, + @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) { + super(id, label, taskService, telemetryService, messageService, contextService); + } + + public run(): TPromise { + if (!this.canRun()) { + return TPromise.as(undefined); + } + return this.taskService.run(this.taskId); + } +} + abstract class OpenTaskConfigurationAction extends Action { private configurationService: IConfigurationService; @@ -196,6 +212,8 @@ abstract class OpenTaskConfigurationAction extends Action { this.outputService = outputService; this.messageService = messageService; this.quickOpenService = quickOpenService; + + } public run(event?: any): TPromise { @@ -421,7 +439,6 @@ class RunTaskAction extends AbstractTaskAction { } } - class StatusBarItem implements IStatusbarItem { private panelService: IPanelService; @@ -697,7 +714,7 @@ class TaskService extends EventEmitter implements ITaskService { } this.emit(TaskServiceEvents.ConfigChanged); if (this._inTerminal) { - this.readConfiguration().then((config) => { + this.createConfiguration().then((config) => { if (!config) { return; } @@ -720,6 +737,19 @@ class TaskService extends EventEmitter implements ITaskService { }); lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown())); + CommandsRegistry.registerCommand('workbench.action.tasks.runTask', (accessor, arg) => { + if (Types.isString(arg)) { + this.tasks().then(tasks => { + for (let task of tasks) { + if (task.name === arg) { + this.run(task.id); + } + } + }); + } else { + this.quickOpenService.show('task '); + } + }); } public log(value: string): void { @@ -747,7 +777,7 @@ class TaskService extends EventEmitter implements ITaskService { this._taskSystem = new NullTaskSystem(); this._taskSystemPromise = TPromise.as(this._taskSystem); } else { - let clearOutput = true; + let hasError = false; this._taskSystemPromise = TPromise.as(this.configurationService.getConfiguration('tasks')).then((config: TaskConfiguration) => { let parseErrors: string[] = config ? (config).$parseErrors : null; if (parseErrors) { @@ -766,16 +796,16 @@ class TaskService extends EventEmitter implements ITaskService { } let configPromise: TPromise; if (config) { - if (this.isRunnerConfig(config) && this.hasDetectorSupport(config)) { - let fileConfig = config; + if (this.isRunnerConfig(config) && this.hasDetectorSupport(config)) { + let fileConfig = config; configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, fileConfig).detect(true).then((value) => { - clearOutput = this.printStderr(value.stderr); + hasError = this.printStderr(value.stderr); let detectedConfig = value.config; if (!detectedConfig) { return config; } - let result: FileConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig); - let configuredTasks: IStringDictionary = Object.create(null); + let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig); + let configuredTasks: IStringDictionary = Object.create(null); if (!result.tasks) { if (detectedConfig.tasks) { result.tasks = detectedConfig.tasks; @@ -795,7 +825,7 @@ class TaskService extends EventEmitter implements ITaskService { } } else { configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService).detect(true).then((value) => { - clearOutput = this.printStderr(value.stderr); + hasError = this.printStderr(value.stderr); return value.config; }); } @@ -805,16 +835,18 @@ class TaskService extends EventEmitter implements ITaskService { throw new TaskError(Severity.Info, nls.localize('TaskSystem.noConfiguration', 'No task runner configured.'), TaskErrors.NotConfigured); } let result: ITaskSystem = null; - let parseResult = FileConfig.parse(config, this); + let parseResult = TaskConfig.parse(config, this); if (!parseResult.validationStatus.isOK()) { this.outputChannel.show(true); + hasError = true; } if (parseResult.validationStatus.isFatal()) { throw new TaskError(Severity.Error, nls.localize('TaskSystem.fatalError', 'The provided task configuration has validation errors. See tasks output log for details.'), TaskErrors.ConfigValidationError); } if (this.isRunnerConfig(config)) { this._inTerminal = false; - result = new ProcessRunnerSystem(parseResult.configuration, this.markerService, this.modelService, this.telemetryService, this.outputService, this.configurationResolverService, TaskService.OutputChannelId, clearOutput); + result = new ProcessRunnerSystem(parseResult.configuration, this.markerService, this.modelService, + this.telemetryService, this.outputService, this.configurationResolverService, TaskService.OutputChannelId, hasError); } else if (this.isTerminalConfig(config)) { this._inTerminal = true; result = new TerminalTaskSystem( @@ -842,7 +874,7 @@ class TaskService extends EventEmitter implements ITaskService { return this._taskSystemPromise; } - private readConfiguration(): TPromise { + private createConfiguration(): TPromise { let config = this.configurationService.getConfiguration('tasks'); let parseErrors: string[] = config ? (config).$parseErrors : null; if (parseErrors) { @@ -861,16 +893,16 @@ class TaskService extends EventEmitter implements ITaskService { } let configPromise: TPromise; if (config) { - if (this.isRunnerConfig(config) && this.hasDetectorSupport(config)) { - let fileConfig = config; + if (this.isRunnerConfig(config) && this.hasDetectorSupport(config)) { + let fileConfig = config; configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, fileConfig).detect(true).then((value) => { this.printStderr(value.stderr); let detectedConfig = value.config; if (!detectedConfig) { return config; } - let result: FileConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig); - let configuredTasks: IStringDictionary = Object.create(null); + let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig); + let configuredTasks: IStringDictionary = Object.create(null); if (!result.tasks) { if (detectedConfig.tasks) { result.tasks = detectedConfig.tasks; @@ -898,7 +930,7 @@ class TaskService extends EventEmitter implements ITaskService { if (!config) { return undefined; } - let parseResult = FileConfig.parse(config, this); + let parseResult = TaskConfig.parse(config, this); if (!parseResult.validationStatus.isOK()) { this.showOutput(); } @@ -911,10 +943,10 @@ class TaskService extends EventEmitter implements ITaskService { } private printStderr(stderr: string[]): boolean { - let result = true; + let result = false; if (stderr && stderr.length > 0) { stderr.forEach((line) => { - result = false; + result = true; this.outputChannel.append(line + '\n'); }); this.outputChannel.show(true); @@ -934,7 +966,7 @@ class TaskService extends EventEmitter implements ITaskService { return this._inTerminal !== void 0 && this._inTerminal; } - private hasDetectorSupport(config: FileConfig.ExternalTaskRunnerConfiguration): boolean { + private hasDetectorSupport(config: TaskConfig.ExternalTaskRunnerConfiguration): boolean { if (!config.command) { return false; } @@ -1122,7 +1154,7 @@ class TaskService extends EventEmitter implements ITaskService { } } -let tasksCategory = nls.localize('tasksCategory', "Tasks"); + let workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureTaskRunnerAction, ConfigureTaskRunnerAction.ID, ConfigureTaskRunnerAction.TEXT), 'Tasks: Configure Task Runner', tasksCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(BuildAction, BuildAction.ID, BuildAction.TEXT, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B }), 'Tasks: Run Build Task', tasksCategory); @@ -1131,7 +1163,9 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(TestAc // workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CleanAction, CleanAction.ID, CleanAction.TEXT), tasksCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminateAction, TerminateAction.ID, TerminateAction.TEXT), 'Tasks: Terminate Running Task', tasksCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogAction, ShowLogAction.ID, ShowLogAction.TEXT), 'Tasks: Show Task Log', tasksCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunTaskAction, RunTaskAction.ID, RunTaskAction.TEXT), 'Tasks: Run Task', tasksCategory); +// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunTaskAction, RunTaskAction.ID, RunTaskAction.TEXT), 'Tasks: Run Task', tasksCategory); + +MenuRegistry.addCommand({ id: 'workbench.action.tasks.runTask', title: nls.localize('RunTaskAction.label', "Run Task"), category: tasksCategory }); // Task Service registerSingleton(ITaskService, TaskService); diff --git a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts index 1e61ccc1d2b..255df6ee251 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts @@ -20,7 +20,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import * as FileConfig from './processRunnerConfiguration'; +import * as TaskConfig from '../common/taskConfiguration'; let build: string = 'build'; let test: string = 'test'; @@ -109,7 +109,7 @@ class GruntTaskMatcher implements TaskDetectorMatcher { } export interface DetectorResult { - config: FileConfig.ExternalTaskRunnerConfiguration; + config: TaskConfig.ExternalTaskRunnerConfiguration; stdout: string[]; stderr: string[]; } @@ -143,12 +143,12 @@ export class ProcessRunnerDetector { private fileService: IFileService; private contextService: IWorkspaceContextService; private configurationResolverService: IConfigurationResolverService; - private taskConfiguration: FileConfig.ExternalTaskRunnerConfiguration; + private taskConfiguration: TaskConfig.ExternalTaskRunnerConfiguration; private _stderr: string[]; private _stdout: string[]; private _cwd: string; - constructor(fileService: IFileService, contextService: IWorkspaceContextService, configurationResolverService: IConfigurationResolverService, config: FileConfig.ExternalTaskRunnerConfiguration = null) { + constructor(fileService: IFileService, contextService: IWorkspaceContextService, configurationResolverService: IConfigurationResolverService, config: TaskConfig.ExternalTaskRunnerConfiguration = null) { this.fileService = fileService; this.contextService = contextService; this.configurationResolverService = configurationResolverService; @@ -225,27 +225,27 @@ export class ProcessRunnerDetector { return result; } - private tryDetectGulp(list: boolean): TPromise<{ config: FileConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> { + private tryDetectGulp(list: boolean): TPromise<{ config: TaskConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> { return this.fileService.resolveFile(this.contextService.toResource('gulpfile.js')).then((stat) => { let config = ProcessRunnerDetector.detectorConfig('gulp'); let process = new LineProcess('gulp', [config.arg, '--no-color'], true, { cwd: this._cwd }); return this.runDetection(process, 'gulp', true, config.matcher, ProcessRunnerDetector.DefaultProblemMatchers, list); - }, (err: any): FileConfig.ExternalTaskRunnerConfiguration => { + }, (err: any): TaskConfig.ExternalTaskRunnerConfiguration => { return null; }); } - private tryDetectGrunt(list: boolean): TPromise<{ config: FileConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> { + private tryDetectGrunt(list: boolean): TPromise<{ config: TaskConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> { return this.fileService.resolveFile(this.contextService.toResource('Gruntfile.js')).then((stat) => { let config = ProcessRunnerDetector.detectorConfig('grunt'); let process = new LineProcess('grunt', [config.arg, '--no-color'], true, { cwd: this._cwd }); return this.runDetection(process, 'grunt', true, config.matcher, ProcessRunnerDetector.DefaultProblemMatchers, list); - }, (err: any): FileConfig.ExternalTaskRunnerConfiguration => { + }, (err: any): TaskConfig.ExternalTaskRunnerConfiguration => { return null; }); } - private tryDetectJake(list: boolean): TPromise<{ config: FileConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> { + private tryDetectJake(list: boolean): TPromise<{ config: TaskConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> { let run = () => { let config = ProcessRunnerDetector.detectorConfig('jake'); let process = new LineProcess('jake', [config.arg], true, { cwd: this._cwd }); @@ -256,7 +256,7 @@ export class ProcessRunnerDetector { }, (err: any) => { return this.fileService.resolveFile(this.contextService.toResource('Jakefile.js')).then((stat) => { return run(); - }, (err: any): FileConfig.ExternalTaskRunnerConfiguration => { + }, (err: any): TaskConfig.ExternalTaskRunnerConfiguration => { return null; }); }); @@ -276,7 +276,7 @@ export class ProcessRunnerDetector { } return { config: null, stdout: this._stdout, stderr: this._stderr }; } - let result: FileConfig.ExternalTaskRunnerConfiguration = { + let result: TaskConfig.ExternalTaskRunnerConfiguration = { version: ProcessRunnerDetector.Version, command: command, isShellCommand: isShellCommand @@ -314,8 +314,8 @@ export class ProcessRunnerDetector { }); } - private createTaskDescriptions(tasks: string[], problemMatchers: string[], list: boolean): FileConfig.TaskDescription[] { - let taskConfigs: FileConfig.TaskDescription[] = []; + private createTaskDescriptions(tasks: string[], problemMatchers: string[], list: boolean): TaskConfig.TaskDescription[] { + let taskConfigs: TaskConfig.TaskDescription[] = []; if (list) { tasks.forEach((task) => { taskConfigs.push({ diff --git a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts index c76fb34e5a5..94ffda79681 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts @@ -53,7 +53,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { private activeTaskPromise: TPromise; constructor(configuration: TaskRunnerConfiguration, markerService: IMarkerService, modelService: IModelService, telemetryService: ITelemetryService, - outputService: IOutputService, configurationResolverService: IConfigurationResolverService, outputChannelId: string, clearOutput: boolean = true) { + outputService: IOutputService, configurationResolverService: IConfigurationResolverService, outputChannelId: string, hasErrors: boolean) { super(); this.configuration = configuration; this.markerService = markerService; @@ -66,11 +66,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { this.activeTaskIdentifier = null; this.activeTaskPromise = null; this.outputChannel = this.outputService.getChannel(outputChannelId); - - if (clearOutput) { - this.clearOutput(); - } - this.errorsShown = false; + this.errorsShown = !hasErrors; } diff --git a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts index 5ca2adadf50..1aed4095f10 100644 --- a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts @@ -12,7 +12,7 @@ import * as Platform from 'vs/base/common/platform'; import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/platform/markers/common/problemMatcher'; import * as TaskSystem from 'vs/workbench/parts/tasks/common/taskSystem'; -import { parse, ParseResult, ILogger, ExternalTaskRunnerConfiguration } from 'vs/workbench/parts/tasks/node/processRunnerConfiguration'; +import { parse, ParseResult, ILogger, ExternalTaskRunnerConfiguration } from 'vs/workbench/parts/tasks/common/taskConfiguration'; class Logger implements ILogger { public receivedMessage: boolean = false; From 2a7becfbab84e20c1b945a4ade177b40a9cfccf7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 Feb 2017 13:17:47 -0800 Subject: [PATCH 005/140] Get web and relative paths working in terminal Part of #7321 --- .../terminal/electron-browser/media/xterm.css | 10 +++++ .../electron-browser/terminalInstance.ts | 43 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css index 95774837462..56814595454 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -58,6 +58,16 @@ opacity: 0 !important; } +.monaco-workbench .panel.integrated-terminal .xterm a { + color: inherit; + text-decoration: none; +} + +.monaco-workbench .panel.integrated-terminal .xterm a:hover { + cursor: pointer; + text-decoration: underline; +} + .monaco-workbench .panel.integrated-terminal .xterm:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus .reverse-video, .monaco-workbench .panel.integrated-terminal .xterm:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar):focus .reverse-video { color: #CCC; } .vs-dark .monaco-workbench .panel.integrated-terminal .xterm:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus .reverse-video, diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index f5c87ee09d8..79c3fdb7aa6 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; +import * as pfs from 'vs/base/node/pfs'; import DOM = require('vs/base/browser/dom'); import Event, { Emitter } from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; @@ -10,9 +12,9 @@ import cp = require('child_process'); import lifecycle = require('vs/base/common/lifecycle'); import nls = require('vs/nls'); import os = require('os'); -import path = require('path'); import platform = require('vs/base/common/platform'); import xterm = require('xterm'); +import { TPromise } from 'vs/base/common/winjs.base'; import { Dimension } from 'vs/base/browser/builder'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -22,6 +24,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; import { ITerminalProcessFactory } from 'vs/workbench/parts/terminal/electron-browser/terminal'; import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; @@ -29,6 +32,13 @@ import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-brows /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; +/** A regex that matches paths in the form /path, ~/path, ./path, ../path */ +const pathPrefix = '(\\.\\.?|\\~)'; +const pathStartClause = '\\/'; +const excludedPathCharactersClause = '[^\\0\\s!$`&*()+\'":;]'; // '":; are allowed in paths but they are often separators so ignore them +const escapedExcludedPathCharactersClause = '(\\\\s|\\\\!|\\\\$|\\\\`|\\\\&|\\\\*|(|)|\\+)'; +const LOCAL_UNIX_LIKE_LINK_REGEX = new RegExp('(' + pathPrefix + '?(' + pathStartClause + '(' + excludedPathCharactersClause + '|' + escapedExcludedPathCharactersClause + ')+)+)'); + class StandardTerminalProcessFactory implements ITerminalProcessFactory { public create(env: { [key: string]: string }): cp.ChildProcess { return cp.fork('./terminalProcess', [], { @@ -80,7 +90,8 @@ export class TerminalInstance implements ITerminalInstance { @IKeybindingService private _keybindingService: IKeybindingService, @IMessageService private _messageService: IMessageService, @IPanelService private _panelService: IPanelService, - @IWorkspaceContextService private _contextService: IWorkspaceContextService + @IWorkspaceContextService private _contextService: IWorkspaceContextService, + @IWorkbenchEditorService private _editorService: IWorkbenchEditorService ) { this._instanceDisposables = []; this._processDisposables = []; @@ -139,6 +150,7 @@ export class TerminalInstance implements ITerminalInstance { this._xtermElement = document.createElement('div'); this._xterm.open(this._xtermElement); + this._xterm.registerLinkMatcher(LOCAL_UNIX_LIKE_LINK_REGEX, (url) => this._openRelativeUnixLikeLink(url), 1); this._xterm.attachCustomKeydownHandler((event: KeyboardEvent) => { // Disable all input if the terminal is exiting if (this._isExiting) { @@ -218,6 +230,33 @@ export class TerminalInstance implements ITerminalInstance { this.updateConfig(); } + private _openRelativeUnixLikeLink(link: string): TPromise { + // Resolve ~ -> $HOME + if (link.charAt(0) === '~') { + link = process.env.HOME + link.substring(1); + } + + // Resolve workspace path . / .. -> /. / { + if (!isFile) { + return void 0; + } + return this._editorService.openEditor({ resource }).then(() => void 0); + }); + } + public hasSelection(): boolean { return !document.getSelection().isCollapsed; } From 20ede81ec107ec8aa564ddb06629d255a633078a Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 10 Feb 2017 13:18:48 -0800 Subject: [PATCH 006/140] Add a separate command for copying text with syntax highlighting --- .../controller/input/textAreaWrapper.ts | 4 +++ .../common/config/commonEditorConfig.ts | 6 ---- src/vs/editor/common/config/defaultConfig.ts | 1 - .../common/controller/textAreaHandler.ts | 5 ++- .../editor/common/controller/textAreaState.ts | 1 + src/vs/editor/common/editorCommon.ts | 8 ----- src/vs/editor/common/standalone/themes.ts | 4 +-- .../contrib/clipboard/browser/clipboard.ts | 33 +++++++++++++++++++ src/vs/monaco.d.ts | 5 --- 9 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/browser/controller/input/textAreaWrapper.ts b/src/vs/editor/browser/controller/input/textAreaWrapper.ts index 35c8884eea9..ca6b62bf37c 100644 --- a/src/vs/editor/browser/controller/input/textAreaWrapper.ts +++ b/src/vs/editor/browser/controller/input/textAreaWrapper.ts @@ -61,6 +61,10 @@ class ClipboardEventWrapper implements IClipboardEvent { throw new Error('ClipboardEventWrapper.getTextData: Cannot use text data!'); } + + public forceCopyWithSyntaxHighlighting(): boolean { + return JSON.parse(window.localStorage.getItem('forceCopyWithSyntaxHighlighting')); + } } class KeyboardEventWrapper implements IKeyboardEventWrapper { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index b3744d95233..dbb4051faf3 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -294,7 +294,6 @@ class InternalEditorOptionsHelper { acceptSuggestionOnCommitCharacter: toBoolean(opts.acceptSuggestionOnCommitCharacter), snippetSuggestions: opts.snippetSuggestions, emptySelectionClipboard: opts.emptySelectionClipboard, - richTextClipboard: opts.richTextClipboard, tabCompletion: opts.tabCompletion, wordBasedSuggestions: opts.wordBasedSuggestions, suggestFontSize: opts.suggestFontSize, @@ -705,11 +704,6 @@ const editorConfiguration: IConfigurationNode = { 'default': DefaultConfig.editor.emptySelectionClipboard, 'description': nls.localize('emptySelectionClipboard', "Controls whether copying without a selection copies the current line.") }, - 'editor.richTextClipboard': { - 'type': 'boolean', - 'default': DefaultConfig.editor.richTextClipboard, - 'description': nls.localize('richTextClipboard', "Controls whether copying text with colorization to the clipboard.") - }, 'editor.wordBasedSuggestions': { 'type': 'boolean', 'default': DefaultConfig.editor.wordBasedSuggestions, diff --git a/src/vs/editor/common/config/defaultConfig.ts b/src/vs/editor/common/config/defaultConfig.ts index 098d2e8d7d1..2ce761385ae 100644 --- a/src/vs/editor/common/config/defaultConfig.ts +++ b/src/vs/editor/common/config/defaultConfig.ts @@ -90,7 +90,6 @@ class ConfigClass implements IConfiguration { acceptSuggestionOnCommitCharacter: true, snippetSuggestions: 'bottom', emptySelectionClipboard: true, - richTextClipboard: false, tabCompletion: false, wordBasedSuggestions: true, suggestFontSize: 0, diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 70835df946b..10afb4a39d4 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -324,7 +324,10 @@ export class TextAreaHandler extends Disposable { private _ensureClipboardGetsEditorSelection(e: IClipboardEvent): void { let whatToCopy = this.model.getPlainTextToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); if (e.canUseTextData()) { - let whatHTMLToCopy = this.model.getHTMLToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); + let whatHTMLToCopy; + if (whatToCopy.length < 64000 || e.forceCopyWithSyntaxHighlighting()) { + whatHTMLToCopy = this.model.getHTMLToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); + } e.setTextData(whatToCopy, whatHTMLToCopy); } else { this.setTextAreaState('copy or cut', this.textAreaState.fromText(whatToCopy), false); diff --git a/src/vs/editor/common/controller/textAreaState.ts b/src/vs/editor/common/controller/textAreaState.ts index 568ba1e959c..939d505f192 100644 --- a/src/vs/editor/common/controller/textAreaState.ts +++ b/src/vs/editor/common/controller/textAreaState.ts @@ -15,6 +15,7 @@ export interface IClipboardEvent { canUseTextData(): boolean; setTextData(text: string, richText?: string): void; getTextData(): string; + forceCopyWithSyntaxHighlighting(): boolean; } export interface ICompositionEvent { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 8e2d1a0d5d5..7fe393ab654 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -426,10 +426,6 @@ export interface IEditorOptions { * Copying without a selection copies the current line. */ emptySelectionClipboard?: boolean; - /** - * Copying text with colorization to the clipboard - */ - richTextClipboard?: boolean; /** * Enable tab completion. Defaults to 'false' */ @@ -910,7 +906,6 @@ export class EditorContribOptions { readonly acceptSuggestionOnCommitCharacter: boolean; readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'; readonly emptySelectionClipboard: boolean; - readonly richTextClipboard: boolean; readonly tabCompletion: boolean; readonly wordBasedSuggestions: boolean; readonly suggestFontSize: number; @@ -937,7 +932,6 @@ export class EditorContribOptions { acceptSuggestionOnCommitCharacter: boolean; snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'; emptySelectionClipboard: boolean; - richTextClipboard: boolean; tabCompletion: boolean; wordBasedSuggestions: boolean; suggestFontSize: number; @@ -960,7 +954,6 @@ export class EditorContribOptions { this.acceptSuggestionOnCommitCharacter = Boolean(source.acceptSuggestionOnCommitCharacter); this.snippetSuggestions = source.snippetSuggestions; this.emptySelectionClipboard = source.emptySelectionClipboard; - this.richTextClipboard = source.richTextClipboard; this.tabCompletion = source.tabCompletion; this.wordBasedSuggestions = source.wordBasedSuggestions; this.suggestFontSize = source.suggestFontSize; @@ -989,7 +982,6 @@ export class EditorContribOptions { && this.acceptSuggestionOnCommitCharacter === other.acceptSuggestionOnCommitCharacter && this.snippetSuggestions === other.snippetSuggestions && this.emptySelectionClipboard === other.emptySelectionClipboard - && this.richTextClipboard === other.richTextClipboard && this.tabCompletion === other.tabCompletion && this.wordBasedSuggestions === other.wordBasedSuggestions && this.suggestFontSize === other.suggestFontSize diff --git a/src/vs/editor/common/standalone/themes.ts b/src/vs/editor/common/standalone/themes.ts index 580b1188d93..98ec0a458e9 100644 --- a/src/vs/editor/common/standalone/themes.ts +++ b/src/vs/editor/common/standalone/themes.ts @@ -70,7 +70,7 @@ export const vs: IThemeRule[] = [ /* -------------------------------- Begin vs-dark tokens -------------------------------- */ export const vs_dark: IThemeRule[] = [ - { token: '', foreground: 'D4D4D4' }, + { token: '', foreground: 'D4D4D4', background: '1E1E1E' }, { token: 'invalid', foreground: 'f44747' }, { token: 'emphasis', fontStyle: 'italic' }, { token: 'strong', fontStyle: 'bold' }, @@ -130,7 +130,7 @@ export const vs_dark: IThemeRule[] = [ /* -------------------------------- Begin hc-black tokens -------------------------------- */ export const hc_black: IThemeRule[] = [ - { token: '', foreground: 'FFFFFF' }, + { token: '', foreground: 'FFFFFF', background: '000000' }, { token: 'invalid', foreground: 'f44747' }, { token: 'emphasis', fontStyle: 'italic' }, { token: 'strong', fontStyle: 'bold' }, diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 1714c171e3c..06ccce47e31 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -136,3 +136,36 @@ class ExecCommandPasteAction extends ExecCommandAction { }); } } + +@editorAction +class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { + + constructor() { + super('copy', { + id: 'editor.action.clipboardCopyWithSyntaxHighlightingAction', + label: nls.localize('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"), + alias: 'Copy With Syntax Highlighting', + precondition: null, + kbOpts: { + kbExpr: EditorContextKeys.TextFocus, + primary: null + }, + menuOpts: { + group: CLIPBOARD_CONTEXT_MENU_GROUP, + order: 2 + } + }); + } + + public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void { + var enableEmptySelectionClipboard = editor.getConfiguration().contribInfo.emptySelectionClipboard && browser.enableEmptySelectionClipboard; + + if (!enableEmptySelectionClipboard && editor.getSelection().isEmpty()) { + return; + } + + window.localStorage.setItem('forceCopyWithSyntaxHighlighting', 'true'); + super.run(accessor, editor); + window.localStorage.setItem('forceCopyWithSyntaxHighlighting', 'false'); + } +} diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 55ce504574e..832a15a0f11 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1348,10 +1348,6 @@ declare module monaco.editor { * Copying without a selection copies the current line. */ emptySelectionClipboard?: boolean; - /** - * Copying text with colorization to the clipboard - */ - richTextClipboard?: boolean; /** * Enable tab completion. Defaults to 'false' */ @@ -1560,7 +1556,6 @@ declare module monaco.editor { readonly acceptSuggestionOnCommitCharacter: boolean; readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'; readonly emptySelectionClipboard: boolean; - readonly richTextClipboard: boolean; readonly tabCompletion: boolean; readonly wordBasedSuggestions: boolean; readonly suggestFontSize: number; From 57fbdeff2958d10f493ca2cf54d84c13d2373e4d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 Feb 2017 13:22:42 -0800 Subject: [PATCH 007/140] Prevent terminal link matcher on Windows for now Only unix-like paths are supported right now --- .../parts/terminal/electron-browser/terminalInstance.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 79c3fdb7aa6..bf41bdd9e6b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -150,7 +150,9 @@ export class TerminalInstance implements ITerminalInstance { this._xtermElement = document.createElement('div'); this._xterm.open(this._xtermElement); - this._xterm.registerLinkMatcher(LOCAL_UNIX_LIKE_LINK_REGEX, (url) => this._openRelativeUnixLikeLink(url), 1); + if (!platform.isWindows) { + this._xterm.registerLinkMatcher(LOCAL_UNIX_LIKE_LINK_REGEX, (url) => this._openRelativeUnixLikeLink(url), 1); + } this._xterm.attachCustomKeydownHandler((event: KeyboardEvent) => { // Disable all input if the terminal is exiting if (this._isExiting) { From 2bfb0f301ad4153b6a1ea119cb48d431d6b03663 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 Feb 2017 13:53:05 -0800 Subject: [PATCH 008/140] Pull link handling code into object owned by service --- .../electron-browser/terminalInstance.ts | 42 +--------- .../electron-browser/terminalLinkHandler.ts | 80 +++++++++++++++++++ .../electron-browser/terminalService.ts | 6 +- 3 files changed, 88 insertions(+), 40 deletions(-) create mode 100644 src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index bf41bdd9e6b..ab7cba19838 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import * as pfs from 'vs/base/node/pfs'; import DOM = require('vs/base/browser/dom'); import Event, { Emitter } from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; @@ -14,7 +13,6 @@ import nls = require('vs/nls'); import os = require('os'); import platform = require('vs/base/common/platform'); import xterm = require('xterm'); -import { TPromise } from 'vs/base/common/winjs.base'; import { Dimension } from 'vs/base/browser/builder'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -28,17 +26,11 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; +import { TerminalLinkHandler } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; -/** A regex that matches paths in the form /path, ~/path, ./path, ../path */ -const pathPrefix = '(\\.\\.?|\\~)'; -const pathStartClause = '\\/'; -const excludedPathCharactersClause = '[^\\0\\s!$`&*()+\'":;]'; // '":; are allowed in paths but they are often separators so ignore them -const escapedExcludedPathCharactersClause = '(\\\\s|\\\\!|\\\\$|\\\\`|\\\\&|\\\\*|(|)|\\+)'; -const LOCAL_UNIX_LIKE_LINK_REGEX = new RegExp('(' + pathPrefix + '?(' + pathStartClause + '(' + excludedPathCharactersClause + '|' + escapedExcludedPathCharactersClause + ')+)+)'); - class StandardTerminalProcessFactory implements ITerminalProcessFactory { public create(env: { [key: string]: string }): cp.ChildProcess { return cp.fork('./terminalProcess', [], { @@ -84,6 +76,7 @@ export class TerminalInstance implements ITerminalInstance { public constructor( private _terminalFocusContextKey: IContextKey, private _configHelper: TerminalConfigHelper, + private _linkHandler: TerminalLinkHandler, private _container: HTMLElement, private _shellLaunchConfig: IShellLaunchConfig, @IContextKeyService private _contextKeyService: IContextKeyService, @@ -150,9 +143,7 @@ export class TerminalInstance implements ITerminalInstance { this._xtermElement = document.createElement('div'); this._xterm.open(this._xtermElement); - if (!platform.isWindows) { - this._xterm.registerLinkMatcher(LOCAL_UNIX_LIKE_LINK_REGEX, (url) => this._openRelativeUnixLikeLink(url), 1); - } + this._xterm.registerLinkMatcher(this._linkHandler.localLinkRegex, (url) => this._linkHandler.handleLocalLink(url), 1); this._xterm.attachCustomKeydownHandler((event: KeyboardEvent) => { // Disable all input if the terminal is exiting if (this._isExiting) { @@ -232,33 +223,6 @@ export class TerminalInstance implements ITerminalInstance { this.updateConfig(); } - private _openRelativeUnixLikeLink(link: string): TPromise { - // Resolve ~ -> $HOME - if (link.charAt(0) === '~') { - link = process.env.HOME + link.substring(1); - } - - // Resolve workspace path . / .. -> /. / { - if (!isFile) { - return void 0; - } - return this._editorService.openEditor({ resource }).then(() => void 0); - }); - } - public hasSelection(): boolean { return !document.getSelection().isCollapsed; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts new file mode 100644 index 00000000000..b7404c781fd --- /dev/null +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as pfs from 'vs/base/node/pfs'; +import Uri from 'vs/base/common/uri'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { Platform } from 'vs/base/common/platform'; +import { TPromise } from 'vs/base/common/winjs.base'; + +const pathPrefix = '(\\.\\.?|\\~)'; +const pathStartClause = '\\/'; +const excludedPathCharactersClause = '[^\\0\\s!$`&*()+\'":;]'; // '":; are allowed in paths but they are often separators so ignore them +const escapedExcludedPathCharactersClause = '(\\\\s|\\\\!|\\\\$|\\\\`|\\\\&|\\\\*|(|)|\\+)'; +/** A regex that matches paths in the form /path, ~/path, ./path, ../path */ +const UNIX_LIKE_LOCAL_LINK_REGEX = new RegExp('(' + pathPrefix + '?(' + pathStartClause + '(' + excludedPathCharactersClause + '|' + escapedExcludedPathCharactersClause + ')+)+)'); + +const winPathPrefix = '(\\.\\.?|\\~)'; +const winPathStartClause = '\\/'; +const winExcludedPathCharactersClause = '[^\\0\\s!$`&*()+\'":;]'; // '":; are allowed in paths but they are often separators so ignore them +const winEscapedExcludedPathCharactersClause = '(\\\\s|\\\\!|\\\\$|\\\\`|\\\\&|\\\\*|(|)|\\+)'; +/** A regex that matches paths in the form c:\path, ~\path, .\path */ +const WINDOWS_LOCAL_LINK_REGEX = new RegExp('(' + winPathPrefix + '?(' + winPathStartClause + '(' + winExcludedPathCharactersClause + '|' + winEscapedExcludedPathCharactersClause + ')+)+)'); + +export class TerminalLinkHandler { + constructor( + private _platform: Platform, + @IWorkbenchEditorService private _editorService: IWorkbenchEditorService, + @IWorkspaceContextService private _contextService: IWorkspaceContextService + ) { + } + + public get localLinkRegex(): RegExp { + if (this._platform === Platform.Windows) { + return WINDOWS_LOCAL_LINK_REGEX; + } + return UNIX_LIKE_LOCAL_LINK_REGEX; + } + + public handleLocalLink(link: string): TPromise { + if (this._platform === Platform.Windows) { + return this._handleWindowsLocalLink(link); + } + return this._handleUnixLikeLocalLink(link); + } + + private _handleUnixLikeLocalLink(link: string): TPromise { + // Resolve ~ -> $HOME + if (link.charAt(0) === '~') { + link = process.env.HOME + link.substring(1); + } + + // Resolve workspace path . / .. -> /. / { + if (!isFile) { + return void 0; + } + return this._editorService.openEditor({ resource }).then(() => void 0); + }); + } + + private _handleWindowsLocalLink(link: string): TPromise { + return null; + } +} diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 5f7640cd8e7..0b418320fbd 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -15,12 +15,14 @@ import { ITerminalInstance, ITerminalService, IShellLaunchConfig, KEYBINDING_CON import { TPromise } from 'vs/base/common/winjs.base'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; +import { TerminalLinkHandler } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler'; export class TerminalService implements ITerminalService { public _serviceBrand: any; private _activeTerminalInstanceIndex: number; private _configHelper: TerminalConfigHelper; + private _linkHandler: TerminalLinkHandler; private _onActiveInstanceChanged: Emitter; private _onInstanceDisposed: Emitter; private _onInstanceProcessIdReady: Emitter; @@ -57,7 +59,8 @@ export class TerminalService implements ITerminalService { this._configurationService.onDidUpdateConfiguration(() => this.updateConfig()); this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService); - this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, platform.platform); + this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, platform.platform); + this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, platform.platform); this.onInstanceDisposed((terminalInstance) => { this._removeInstance(terminalInstance); }); } @@ -65,6 +68,7 @@ export class TerminalService implements ITerminalService { let terminalInstance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._configHelper, + this._linkHandler, this._terminalContainer, shell); terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); From 78d081356b52887c6cdb2d676a8ade61a5b6e94c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 Feb 2017 14:04:16 -0800 Subject: [PATCH 009/140] Support windows link regex --- .../electron-browser/terminalLinkHandler.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts index b7404c781fd..9379a77d6ed 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -12,18 +12,18 @@ import { Platform } from 'vs/base/common/platform'; import { TPromise } from 'vs/base/common/winjs.base'; const pathPrefix = '(\\.\\.?|\\~)'; -const pathStartClause = '\\/'; +const pathSeparatorClause = '\\/'; const excludedPathCharactersClause = '[^\\0\\s!$`&*()+\'":;]'; // '":; are allowed in paths but they are often separators so ignore them const escapedExcludedPathCharactersClause = '(\\\\s|\\\\!|\\\\$|\\\\`|\\\\&|\\\\*|(|)|\\+)'; /** A regex that matches paths in the form /path, ~/path, ./path, ../path */ -const UNIX_LIKE_LOCAL_LINK_REGEX = new RegExp('(' + pathPrefix + '?(' + pathStartClause + '(' + excludedPathCharactersClause + '|' + escapedExcludedPathCharactersClause + ')+)+)'); +const UNIX_LIKE_LOCAL_LINK_REGEX = new RegExp('(' + pathPrefix + '?(' + pathSeparatorClause + '(' + excludedPathCharactersClause + '|' + escapedExcludedPathCharactersClause + ')+)+)'); -const winPathPrefix = '(\\.\\.?|\\~)'; -const winPathStartClause = '\\/'; -const winExcludedPathCharactersClause = '[^\\0\\s!$`&*()+\'":;]'; // '":; are allowed in paths but they are often separators so ignore them -const winEscapedExcludedPathCharactersClause = '(\\\\s|\\\\!|\\\\$|\\\\`|\\\\&|\\\\*|(|)|\\+)'; +const winPathPrefix = '([a-zA-Z]:|\\.\\.?|\\~)'; +const winPathSeparatorClause = '(\\\\|\\/)'; +const winExcludedPathCharactersClause = '[^\\0<>\\?\\|\\/\\s!$`&*()+\'":;]'; /** A regex that matches paths in the form c:\path, ~\path, .\path */ -const WINDOWS_LOCAL_LINK_REGEX = new RegExp('(' + winPathPrefix + '?(' + winPathStartClause + '(' + winExcludedPathCharactersClause + '|' + winEscapedExcludedPathCharactersClause + ')+)+)'); +console.log('(' + winPathPrefix + '?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)'); +const WINDOWS_LOCAL_LINK_REGEX = new RegExp('(' + winPathPrefix + '?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)'); export class TerminalLinkHandler { constructor( From 11298b840e16c9ffc9aef46c55496aaf73808337 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 Feb 2017 14:16:10 -0800 Subject: [PATCH 010/140] Add regex tests --- .../electron-browser/terminalLinkHandler.ts | 1 - .../terminalLinkHandler.test.ts | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts index 9379a77d6ed..95783d3abcb 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -22,7 +22,6 @@ const winPathPrefix = '([a-zA-Z]:|\\.\\.?|\\~)'; const winPathSeparatorClause = '(\\\\|\\/)'; const winExcludedPathCharactersClause = '[^\\0<>\\?\\|\\/\\s!$`&*()+\'":;]'; /** A regex that matches paths in the form c:\path, ~\path, .\path */ -console.log('(' + winPathPrefix + '?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)'); const WINDOWS_LOCAL_LINK_REGEX = new RegExp('(' + winPathPrefix + '?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)'); export class TerminalLinkHandler { diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts new file mode 100644 index 00000000000..4b4c5ef5118 --- /dev/null +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import { Platform } from 'vs/base/common/platform'; +import { TerminalLinkHandler } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler'; + +suite('Workbench - TerminalLinkHandler', () => { + suite('localLinkRegex', () => { + test('Windows', () => { + const regex = new TerminalLinkHandler(Platform.Windows, null, null).localLinkRegex; + function testLink(link: string) { + assert.equal(` ${link} `.match(regex)[1], link); + assert.equal(`:${link}:`.match(regex)[1], link); + assert.equal(`;${link};`.match(regex)[1], link); + assert.equal(`(${link})`.match(regex)[1], link); + } + testLink('c:\\foo'); + testLink('c:/foo'); + testLink('.\\foo'); + testLink('./foo'); + testLink('..\\foo'); + testLink('../foo'); + testLink('~\\foo'); + testLink('~/foo'); + testLink('c:/a/long/path'); + testLink('c:\\a\\long\\path'); + testLink('c:\\mixed/slash\\path'); + }); + + test('Linux', () => { + const regex = new TerminalLinkHandler(Platform.Linux, null, null).localLinkRegex; + function testLink(link: string) { + assert.equal(` ${link} `.match(regex)[1], link); + assert.equal(`:${link}:`.match(regex)[1], link); + assert.equal(`;${link};`.match(regex)[1], link); + assert.equal(`(${link})`.match(regex)[1], link); + } + testLink('/foo'); + testLink('~/foo'); + testLink('./foo'); + testLink('../foo'); + testLink('/a/long/path'); + }); + }); +}); \ No newline at end of file From f0fd429952e5cd1048284511b1cbc8f0edf56544 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 10 Feb 2017 14:31:43 -0800 Subject: [PATCH 011/140] 65536 is a good limit --- src/vs/editor/common/controller/textAreaHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 10afb4a39d4..3070b0dbbe3 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -325,7 +325,7 @@ export class TextAreaHandler extends Disposable { let whatToCopy = this.model.getPlainTextToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); if (e.canUseTextData()) { let whatHTMLToCopy; - if (whatToCopy.length < 64000 || e.forceCopyWithSyntaxHighlighting()) { + if (whatToCopy.length < 65536 || e.forceCopyWithSyntaxHighlighting()) { whatHTMLToCopy = this.model.getHTMLToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); } e.setTextData(whatToCopy, whatHTMLToCopy); From 86b7ffa88a3b86240977d3d9628d9b6b2801881f Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 10 Feb 2017 15:06:51 -0800 Subject: [PATCH 012/140] copy with syntax highlighting is not possible in IE --- src/vs/editor/contrib/clipboard/browser/clipboard.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 06ccce47e31..98a5ff68702 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -25,6 +25,14 @@ function conditionalEditorAction(testCommand: string) { return editorAction; } +function conditionalCopyWithSyntaxHighlighting() { + if (browser.isIE || !browser.supportsExecCommand('copy')) { + return () => { }; + } + + return editorAction; +} + abstract class ExecCommandAction extends EditorAction { private browserCommand: string; @@ -137,7 +145,7 @@ class ExecCommandPasteAction extends ExecCommandAction { } } -@editorAction +@conditionalCopyWithSyntaxHighlighting() class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { constructor() { From 2399c2697513d811417e8bc5a240b9752416c91e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 Feb 2017 15:03:13 -0800 Subject: [PATCH 013/140] Support windows links --- .../electron-browser/terminalLinkHandler.ts | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts index 95783d3abcb..ebf09bf37b6 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -49,9 +49,26 @@ export class TerminalLinkHandler { private _handleUnixLikeLocalLink(link: string): TPromise { // Resolve ~ -> $HOME if (link.charAt(0) === '~') { + if (!process.env.HOME) { + return TPromise.as(void 0); + } link = process.env.HOME + link.substring(1); } + return this._handleCommonLocalLink(link); + } + private _handleWindowsLocalLink(link: string): TPromise { + // Resolve ~ -> %HOMEDRIVE%\%HOMEPATH% + if (link.charAt(0) === '~') { + if (!process.env.HOMEDRIVE || !process.env.HOMEPATH) { + return TPromise.as(void 0); + } + link = `${process.env.HOMEDRIVE}\\${process.env.HOMEPATH + link.substring(1)}`; + } + return this._handleCommonLocalLink(link); + } + + private _handleCommonLocalLink(link: string): TPromise { // Resolve workspace path . / .. -> /. / void 0); }); } - - private _handleWindowsLocalLink(link: string): TPromise { - return null; - } } From 184e7288cfc74ec73ca96ae2b8da2b21dcf91ad6 Mon Sep 17 00:00:00 2001 From: Chirag Bhatia Date: Sat, 11 Feb 2017 14:34:40 +0530 Subject: [PATCH 014/140] Fixes #17701 - Integrated Terminal Context Menu is triggered via contextmenu event instead of mousedown --- .../electron-browser/terminalPanel.ts | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index e6f78e622e2..241ccd8a126 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -26,6 +26,7 @@ export class TerminalPanel extends Panel { private _actions: IAction[]; private _contextMenuActions: IAction[]; + private _cancelContextMenu: boolean = false; private _currentBaseThemeId: string; private _font: ITerminalFont; private _fontStyleElement: HTMLElement; @@ -159,24 +160,29 @@ export class TerminalPanel extends Panel { } else { terminal.paste(); } - } else { - const standardEvent = new StandardMouseEvent(event); - let anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy }; - this._contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => TPromise.as(this._getContextMenuActions()), - getActionsContext: () => this._parentDomElement, - getKeyBinding: (action) => { - const [kb] = this._keybindingService.lookupKeybindings(action.id); - if (kb) { - return kb; - } - return null; - } - }); + this._cancelContextMenu = true; } } })); + this._register(DOM.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => { + if (!this._cancelContextMenu) { + const standardEvent = new StandardMouseEvent(event); + let anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy }; + this._contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => TPromise.as(this._getContextMenuActions()), + getActionsContext: () => this._parentDomElement, + getKeyBinding: (action) => { + const [kb] = this._keybindingService.lookupKeybindings(action.id); + if (kb) { + return kb; + } + return null; + } + }); + } + this._cancelContextMenu = false; + })); this._register(DOM.addDisposableListener(this._parentDomElement, 'click', (event) => { if (this._terminalService.terminalInstances.length === 0) { return; From 53ba60134848dd502d43bd1eb9b71d521f333832 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sat, 11 Feb 2017 18:38:37 -0800 Subject: [PATCH 015/140] IE and Edge have trouble with setting html content in clipboard --- src/vs/editor/common/controller/textAreaHandler.ts | 2 +- src/vs/editor/contrib/clipboard/browser/clipboard.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 3070b0dbbe3..070a492f173 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -325,7 +325,7 @@ export class TextAreaHandler extends Disposable { let whatToCopy = this.model.getPlainTextToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); if (e.canUseTextData()) { let whatHTMLToCopy; - if (whatToCopy.length < 65536 || e.forceCopyWithSyntaxHighlighting()) { + if (!this.Browser.isEdgeOrIE && (whatToCopy.length < 65536 || e.forceCopyWithSyntaxHighlighting())) { whatHTMLToCopy = this.model.getHTMLToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); } e.setTextData(whatToCopy, whatHTMLToCopy); diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 98a5ff68702..d79059f8d7e 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -26,7 +26,7 @@ function conditionalEditorAction(testCommand: string) { } function conditionalCopyWithSyntaxHighlighting() { - if (browser.isIE || !browser.supportsExecCommand('copy')) { + if (browser.isEdgeOrIE || !browser.supportsExecCommand('copy')) { return () => { }; } From 422d7ff1b78fcac514498921551c33a988c4ad34 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Sun, 12 Feb 2017 19:13:11 +0100 Subject: [PATCH 016/140] update node-debug --- build/gulpfile.vscode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 731724b8066..45cacdf9b58 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.10.6' }, + { name: 'ms-vscode.node-debug', version: '1.10.7' }, { name: 'ms-vscode.node-debug2', version: '1.9.13' } ]; From 36cf071fefff330551ea03313af968f9c74b1158 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 13 Feb 2017 10:04:11 +0100 Subject: [PATCH 017/140] localization clarification fixes #20382 --- src/vs/workbench/parts/debug/electron-browser/debugService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 402492587fd..7f0376cfbcd 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -573,7 +573,7 @@ export class DebugService implements debug.IDebugService { const compound = typeof configurationOrName === 'string' ? this.configurationManager.getCompound(configurationOrName) : null; if (compound) { if (!compound.configurations) { - return TPromise.wrapError(new Error(nls.localize({ key: 'compoundMustHaveConfigurations', comment: ['compound indicates a "compounds" configuration item'] }, + return TPromise.wrapError(new Error(nls.localize({ key: 'compoundMustHaveConfigurations', comment: ['compound indicates a "compounds" configuration item', '"configurations" is an attribute and should not be localized'] }, "Compound must have \"configurations\" attribute set in order to start multiple configurations."))); } From 14187e44ffdb443a15f107c37643dcf10c79b75d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 13 Feb 2017 11:11:31 +0100 Subject: [PATCH 018/140] support commandPalette menu fixes #19345 --- .../actions/browser/menusExtensionPoint.ts | 7 +++--- src/vs/platform/actions/common/actions.ts | 6 +++-- src/vs/platform/actions/common/menu.ts | 25 ++++++++++++++++--- .../quickopen/browser/commandsHandler.ts | 18 ++++++------- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/actions/browser/menusExtensionPoint.ts b/src/vs/platform/actions/browser/menusExtensionPoint.ts index 497a437071f..91cd7b8c64a 100644 --- a/src/vs/platform/actions/browser/menusExtensionPoint.ts +++ b/src/vs/platform/actions/browser/menusExtensionPoint.ts @@ -28,6 +28,7 @@ namespace schema { export function parseMenuId(value: string): MenuId { switch (value) { + case 'commandPalette': return MenuId.CommandPalette; case 'editor/title': return MenuId.EditorTitle; case 'editor/context': return MenuId.EditorContext; case 'explorer/context': return MenuId.ExplorerContext; @@ -224,7 +225,7 @@ ExtensionsRegistry.registerExtensionPoint(value)) { for (let command of value) { handleCommand(command, extension); @@ -259,7 +260,7 @@ ExtensionsRegistry.registerExtensionPoint('menus', [], schema.menusContribtion).setHandler(extensions => { for (let extension of extensions) { - const {value, collector} = extension; + const { value, collector } = extension; forEach(value, entry => { if (!schema.isValidMenuItems(entry.value, collector)) { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 67fe9f67b89..ed8cc1ebd72 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -49,6 +49,7 @@ export class MenuId { static readonly SCMTitle = new MenuId('11'); static readonly SCMResourceGroupContext = new MenuId('12'); static readonly SCMResourceContext = new MenuId('13'); + static readonly CommandPalette = new MenuId('14'); constructor(private _id: string) { @@ -140,17 +141,18 @@ export class MenuItemAction extends ExecuteCommandAction { constructor( item: ICommandAction, + label: string, alt: ICommandAction, arg: any, @ICommandService commandService: ICommandService ) { - super(item.id, item.title, commandService); + super(item.id, label, commandService); this._cssClass = item.iconClass; this._enabled = true; this._arg = arg; this.item = item; - this.alt = alt ? new MenuItemAction(alt, undefined, arg, commandService) : undefined; + this.alt = alt ? new MenuItemAction(alt, alt.title, undefined, arg, commandService) : undefined; } run(): TPromise { diff --git a/src/vs/platform/actions/common/menu.ts b/src/vs/platform/actions/common/menu.ts index 621eee3a73a..3c54f3b75e3 100644 --- a/src/vs/platform/actions/common/menu.ts +++ b/src/vs/platform/actions/common/menu.ts @@ -8,9 +8,12 @@ import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; +import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { values } from 'vs/base/common/collections'; +import { index } from 'vs/base/common/arrays'; type MenuItemGroup = [string, IMenuItem[]]; @@ -21,13 +24,23 @@ export class Menu implements IMenu { private _onDidChange = new Emitter(); constructor( - id: MenuId, + private id: MenuId, startupSignal: TPromise, @ICommandService private _commandService: ICommandService, @IContextKeyService private _contextKeyService: IContextKeyService ) { startupSignal.then(_ => { - const menuItems = MenuRegistry.getMenuItems(id); + let menuItems = MenuRegistry.getMenuItems(id); + + if (id === MenuId.CommandPalette) { + const ids = index(menuItems, i => i.command.id); + const commandMenuItems = values(MenuRegistry.commands) + .filter(c => !ids[c.id]) + .map(command => ({ command })); + + menuItems = [...menuItems, ...commandMenuItems]; + } + const keysFilter = new Set(); let group: MenuItemGroup; @@ -76,7 +89,13 @@ export class Menu implements IMenu { const activeActions: MenuItemAction[] = []; for (const item of items) { if (this._contextKeyService.contextMatchesRules(item.when)) { - const action = new MenuItemAction(item.command, item.alt, arg, this._commandService); + let title = item.command.title; + + if (this.id === MenuId.CommandPalette && item.command.category) { + title = localize('', "{0}: {1}", item.command.category, title); + } + + const action = new MenuItemAction(item.command, title, item.alt, arg, this._commandService); action.order = item.order; //TODO@Ben order is menu item property, not an action property activeActions.push(action); } diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts index 5dac4496a51..ad7f6340bae 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts @@ -16,7 +16,8 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import strings = require('vs/base/common/strings'); import { Mode, IEntryRunContext, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntryGroup, IHighlight, QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { SyncActionDescriptor, ExecuteCommandAction, IMenuService } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; import { Registry } from 'vs/platform/platform'; import { QuickOpenHandler, QuickOpenAction } from 'vs/workbench/browser/quickopen'; @@ -238,7 +239,8 @@ export class CommandsHandler extends QuickOpenHandler { @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IInstantiationService private instantiationService: IInstantiationService, @IKeybindingService private keybindingService: IKeybindingService, - @IMenuService private menuService: IMenuService + @IMenuService private menuService: IMenuService, + @IContextKeyService private contextKeyService: IContextKeyService ) { super(); @@ -274,14 +276,12 @@ export class CommandsHandler extends QuickOpenHandler { const editorEntries = this.editorActionsToEntries(editorActions, searchValue); // Other Actions - const otherActions = this.menuService.getCommandActions().map(command => { - return this.instantiationService.createInstance(ExecuteCommandAction, command.id, - command.category ? nls.localize('', "{0}: {1}", command.category, command.title) : command.title); - }); - const otherEntries = this.otherActionsToEntries(otherActions, searchValue); + const menu = this.menuService.createMenu(MenuId.CommandPalette, this.contextKeyService); + const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []); + const commandEntries = this.commandActionsToEntries(menuActions, searchValue); // Concat - let entries = [...workbenchEntries, ...editorEntries, ...otherEntries]; + let entries = [...workbenchEntries, ...editorEntries, ...commandEntries]; // Remove duplicates entries = arrays.distinct(entries, (entry) => entry.getLabel() + entry.getGroupLabel()); @@ -355,7 +355,7 @@ export class CommandsHandler extends QuickOpenHandler { return entries; } - private otherActionsToEntries(actions: IAction[], searchValue: string): ActionCommandEntry[] { + private commandActionsToEntries(actions: IAction[], searchValue: string): ActionCommandEntry[] { const entries: ActionCommandEntry[] = []; for (let action of actions) { From 4481a75291b7e7bf1cc48255f1703d7a4f30072d Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 13 Feb 2017 15:57:03 +0100 Subject: [PATCH 019/140] debug: respect StartSessionResult --- .../parts/debug/browser/debugActions.ts | 27 ++++++++++++++++--- src/vs/workbench/parts/debug/common/debug.ts | 2 ++ .../debugConfigurationManager.ts | 6 ++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 3bac5c1338a..883004aadfb 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -11,6 +11,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IFileService } from 'vs/platform/files/common/files'; import { IDebugService, IConfig, State, IProcess, IThread, IEnablement, IBreakpoint, IStackFrame, IFunctionBreakpoint, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, IExpression, REPL_ID } from 'vs/workbench/parts/debug/common/debug'; import { Variable, Expression, Thread, Breakpoint, Process } from 'vs/workbench/parts/debug/common/debugModel'; @@ -102,6 +103,11 @@ export class ConfigureAction extends AbstractDebugAction { } } +interface StartSessionResult { + status: 'ok' | 'initialConfiguration' | 'saveConfiguration'; + content?: string; +}; + export class StartAction extends AbstractDebugAction { static ID = 'workbench.action.debug.start'; static LABEL = nls.localize('startDebug', "Start Debugging"); @@ -110,7 +116,8 @@ export class StartAction extends AbstractDebugAction { @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @ICommandService private commandService: ICommandService, - @IWorkspaceContextService private contextService: IWorkspaceContextService + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IFileService private fileService: IFileService ) { super(id, label, 'debug-action start', debugService, keybindingService); this.debugService.getViewModel().onDidSelectConfiguration(() => { @@ -131,7 +138,18 @@ export class StartAction extends AbstractDebugAction { return manager.getStartSessionCommand(configuration ? configuration.type : undefined).then(commandAndType => { configuration = this.massageConfiguartion(configuration); if (commandAndType && commandAndType.command) { - return this.commandService.executeCommand(commandAndType.command, configuration || this.getDefaultConfiguration()); + return this.commandService.executeCommand(commandAndType.command, configuration || this.getDefaultConfiguration()).then((result: StartSessionResult) => { + if (this.contextService.getWorkspace()) { + if (result && result.status === 'initialConfiguration') { + return manager.openConfigFile(false, commandAndType.type); + } + + if (result && result.status === 'saveConfiguration') { + return this.fileService.updateContent(manager.configFileUri, result.content).then(() => manager.openConfigFile(false)); + } + } + return undefined; + }); } if (configName) { @@ -170,9 +188,10 @@ export class RunAction extends StartAction { @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @ICommandService commandService: ICommandService, - @IWorkspaceContextService contextService: IWorkspaceContextService + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IFileService fileService: IFileService ) { - super(id, label, debugService, keybindingService, commandService, contextService); + super(id, label, debugService, keybindingService, commandService, contextService, fileService); } protected getDefaultConfiguration(): any { diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 5a9c891b54a..f25922f6fe1 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -363,6 +363,8 @@ export interface IConfigurationManager { */ getCompound(name: string): ICompound; + configFileUri: uri; + /** * Opens the launch.json file. Creates if it does not exist. */ diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 868fbea41aa..7e16a7906ba 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -342,8 +342,12 @@ export class ConfigurationManager implements debug.IConfigurationManager { return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null); } + public get configFileUri(): uri { + return uri.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '/.vscode/launch.json')); + } + public openConfigFile(sideBySide: boolean, type?: string): TPromise { - const resource = uri.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '/.vscode/launch.json')); + const resource = this.configFileUri; let configFileCreated = false; return this.fileService.resolveContent(resource).then(content => true, err => From 2c69e4e50fd4f5ffd715a9b1dbb3340c08d0380d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 13 Feb 2017 16:43:01 +0100 Subject: [PATCH 020/140] git: renames fixes #18654 --- extensions/git/src/commands.ts | 29 ++++++++++++++++++++--------- extensions/git/src/model.ts | 22 ++++++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 10af98114e3..3746ab9b9f1 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -113,13 +113,13 @@ export class CommandCenter { const right = this.getRightResource(resource); const title = this.getTitle(resource); - if (!left) { - if (!right) { - // TODO - console.error('oh no'); - return; - } + if (!right) { + // TODO + console.error('oh no'); + return; + } + if (!left) { return await commands.executeCommand('vscode.open', right); } @@ -130,11 +130,11 @@ export class CommandCenter { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: - return resource.uri.with({ scheme: 'git', query: 'HEAD' }); + return resource.original.with({ scheme: 'git', query: 'HEAD' }); case Status.MODIFIED: - const uriString = resource.uri.toString(); - const [indexStatus] = this.model.indexGroup.resources.filter(r => r.uri.toString() === uriString); + const uriString = resource.original.toString(); + const [indexStatus] = this.model.indexGroup.resources.filter(r => r.original.toString() === uriString); if (indexStatus) { return resource.uri.with({ scheme: 'git' }); @@ -149,6 +149,8 @@ export class CommandCenter { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: case Status.INDEX_COPIED: + return resource.uri.with({ scheme: 'git' }); + case Status.INDEX_RENAMED: return resource.uri.with({ scheme: 'git' }); @@ -159,6 +161,15 @@ export class CommandCenter { case Status.MODIFIED: case Status.UNTRACKED: case Status.IGNORED: + const uriString = resource.uri.toString(); + const [indexStatus] = this.model.indexGroup.resources.filter(r => r.uri.toString() === uriString); + + if (indexStatus && indexStatus.rename) { + return indexStatus.rename; + } + + return resource.uri; + case Status.BOTH_MODIFIED: return resource.uri; } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index f894ad1998f..80fd9d7191d 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -43,8 +43,17 @@ export enum Status { export class Resource implements SCMResource { - get uri(): Uri { return this._uri; } + get uri(): Uri { + if (this.rename && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED)) { + return this.rename; + } + + return this._uri; + } + get type(): Status { return this._type; } + get original(): Uri { return this._uri; } + get rename(): Uri | undefined { return this._rename; } private static Icons = { light: { @@ -110,8 +119,8 @@ export class Resource implements SCMResource { return { strikeThrough: this.strikeThrough, light, dark }; } - constructor(private _uri: Uri, private _type: Status) { - + constructor(private _uri: Uri, private _type: Status, private _rename?: Uri) { + // console.log(this); } } @@ -435,6 +444,7 @@ export class Model { status.forEach(raw => { const uri = Uri.file(path.join(this.repositoryRoot, raw.path)); + const renameUri = raw.rename ? Uri.file(path.join(this.repositoryRoot, raw.rename)) : undefined; switch (raw.x + raw.y) { case '??': return workingTree.push(new Resource(uri, Status.UNTRACKED)); @@ -454,13 +464,13 @@ export class Model { case 'M': index.push(new Resource(uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break; case 'A': index.push(new Resource(uri, Status.INDEX_ADDED)); break; case 'D': index.push(new Resource(uri, Status.INDEX_DELETED)); break; - case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED/*, raw.rename*/)); break; + case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED, renameUri)); break; case 'C': index.push(new Resource(uri, Status.INDEX_COPIED)); break; } switch (raw.y) { - case 'M': workingTree.push(new Resource(uri, Status.MODIFIED/*, raw.rename*/)); break; - case 'D': workingTree.push(new Resource(uri, Status.DELETED/*, raw.rename*/)); break; + case 'M': workingTree.push(new Resource(uri, Status.MODIFIED, renameUri)); break; + case 'D': workingTree.push(new Resource(uri, Status.DELETED, renameUri)); break; } }); From 5126ba502d9f3621e624b582afd93d0f23331f91 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 13 Feb 2017 10:20:12 -0800 Subject: [PATCH 021/140] Fix #20411 - Ensure result count updated at end --- .../parts/search/browser/searchViewlet.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 5af6f77e762..12dad4f11d8 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -1092,7 +1092,8 @@ export class SearchViewlet extends Viewlet { } else { this.viewModel.searchResult.toggleHighlights(true); // show highlights - // Indicate as status to ARIA + // Indicate final search result count in viewlet label and ARIA + this.updateSearchResultCount(); aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount())); } }; @@ -1158,13 +1159,7 @@ export class SearchViewlet extends Viewlet { autoExpand(false); }).done(null, errors.onUnexpectedError); - // Update results text - const msgWasHidden = this.messages.isHidden(); - const div = this.clearMessage(); - $(div).p({ text: this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount) }); - if (msgWasHidden) { - this.reLayout(); - } + this.updateSearchResultCount(); } if (fileCount > 0) { // since we have results now, enable some actions @@ -1179,6 +1174,15 @@ export class SearchViewlet extends Viewlet { this.viewModel.search(query).done(onComplete, onError, onProgress); } + private updateSearchResultCount(): void { + const msgWasHidden = this.messages.isHidden(); + const div = this.clearMessage(); + $(div).p({ text: this.buildResultCountMessage(this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount()) }); + if (msgWasHidden) { + this.reLayout(); + } + } + private buildResultCountMessage(resultCount: number, fileCount: number): string { if (resultCount === 1 && fileCount === 1) { return nls.localize('search.file.result', "Found {0} result in {1} file", resultCount, fileCount); From 1e75d33fe4ab69a202954bb760a0781ae710add1 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 13 Feb 2017 10:52:48 -0800 Subject: [PATCH 022/140] F -> f --- .../welcome/page/electron-browser/vs_code_welcome_page.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.html b/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.html index e42ea14211e..1274ed31541 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.html +++ b/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.html @@ -9,7 +9,7 @@

Start

    -
  • New File
  • +
  • New file
  • Open folder...
  • Open folder...
  • Clone Git repository...
  • From d3cbe86479e5e1c668e980f7bedb1d6dfd71fcc5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 13 Feb 2017 11:55:17 -0800 Subject: [PATCH 023/140] Fix #20400 - always update the search result count when the tree is updated --- src/vs/workbench/parts/search/browser/searchViewlet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 12dad4f11d8..3458f87b123 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -315,6 +315,7 @@ export class SearchViewlet extends Viewlet { private onSearchResultsChanged(event?: IChangeEvent): TPromise { return this.refreshTree(event).then(() => { this.searchWidget.setReplaceAllActionState(!this.viewModel.searchResult.isEmpty()); + this.updateSearchResultCount(); }); } @@ -1092,8 +1093,7 @@ export class SearchViewlet extends Viewlet { } else { this.viewModel.searchResult.toggleHighlights(true); // show highlights - // Indicate final search result count in viewlet label and ARIA - this.updateSearchResultCount(); + // Indicate final search result count for ARIA aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount())); } }; From 5db8fd0f55a756abfbd2c586e6fbe3860b9e4539 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Feb 2017 12:58:40 -0800 Subject: [PATCH 024/140] Add Command to Go To / Create project configuration for an active js or ts file (#20362) * Add Command to Go To / Create project configuration for an active js or ts file Part of #20356 Adds a new command that opens the jsconfig or tsconfig project configuration file for the currently active file. If one does not exist, displays a quick pick that allows users to learn more and create a config file at the root of their project * Add messages for error cases * Work around ts error --- extensions/typescript/package.json | 25 ++++-- extensions/typescript/package.nls.json | 8 +- extensions/typescript/src/typescriptMain.ts | 92 ++++++++++++++++++++- 3 files changed, 116 insertions(+), 9 deletions(-) diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 415686fe9c9..652728d8a17 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -33,7 +33,9 @@ "onLanguage:jsx-tags", "onCommand:typescript.reloadProjects", "onCommand:javascript.reloadProjects", - "onCommand:typescript.selectTypeScriptVersion" + "onCommand:typescript.selectTypeScriptVersion", + "onCommand:javascript.goToProjectConfig", + "onCommand:typescript.goToProjectConfig" ], "main": "./out/typescriptMain", "enableProposedApi": true, @@ -257,15 +259,28 @@ "commands": [ { "command": "typescript.reloadProjects", - "title": "%typescript.reloadProjects.title%" + "title": "%typescript.reloadProjects.title%", + "category": "TypeScript" }, { "command": "javascript.reloadProjects", - "title": "%javascript.reloadProjects.title%" + "title": "%javascript.reloadProjects.title%", + "category": "JavaScript" }, { "command": "typescript.selectTypeScriptVersion", - "title": "%typescript.selectTypeScriptVersion.title%" + "title": "%typescript.selectTypeScriptVersion.title%", + "category": "TypeScript" + }, + { + "command": "typescript.goToProjectConfig", + "title": "%typescript.goToProjectConfig.title%", + "category": "TypeScript" + }, + { + "command": "javascript.goToProjectConfig", + "title": "%javascript.goToProjectConfig.title%", + "category": "JavaScript" } ], "breakpoints": [ @@ -301,4 +316,4 @@ } ] } -} +} \ No newline at end of file diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index 2d1589b98cf..d925d83f779 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -1,6 +1,6 @@ { - "typescript.reloadProjects.title": "Reload TypeScript Project", - "javascript.reloadProjects.title": "Reload JavaScript Project", + "typescript.reloadProjects.title": "Reload Project", + "javascript.reloadProjects.title": "Reload Project", "configuration.typescript": "TypeScript", "typescript.useCodeSnippetsOnMethodSuggest.dec": "Complete functions with their parameter signature.", "typescript.tsdk.desc": "Specifies the folder path containing the tsserver and lib*.d.ts files to use.", @@ -24,6 +24,8 @@ "format.placeOpenBraceOnNewLineForFunctions": "Defines whether an open brace is put onto a new line for functions or not.", "format.placeOpenBraceOnNewLineForControlBlocks": "Defines whether an open brace is put onto a new line for control blocks or not.", "javascript.validate.enable": "Enable/disable JavaScript validation.", + "typescript.goToProjectConfig.title": "Go to Project Configuration", + "javascript.goToProjectConfig.title": "Go to Project Configuration", "typescript.referencesCodeLens.enabled": "Enable/disable the references code lens.", - "typescript.selectTypeScriptVersion.title": "Select TypeScript version." + "typescript.selectTypeScriptVersion.title": "Select TypeScript Version" } \ No newline at end of file diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 2fe25d90d57..dad29a5ea3f 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -9,12 +9,13 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; -import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri } from 'vscode'; +import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri, MessageItem, TextEditor } from 'vscode'; // This must be the first statement otherwise modules might got loaded with // the wrong locale. import * as nls from 'vscode-nls'; nls.config({ locale: env.language }); +const localize = nls.loadMessageBundle(); import * as path from 'path'; @@ -52,6 +53,16 @@ interface LanguageDescription { configFile: string; } +enum ProjectConfigAction { + None, + CreateConfig, + LearnMore +} + +interface ProjectConfigMessageItem extends MessageItem { + id: ProjectConfigAction; +} + export function activate(context: ExtensionContext): void { const MODE_ID_TS = 'typescript'; const MODE_ID_TSX = 'typescriptreact'; @@ -89,6 +100,15 @@ export function activate(context: ExtensionContext): void { client.onVersionStatusClicked(); })); + const goToProjectConfig = (isTypeScript: boolean) => { + const editor = window.activeTextEditor; + if (editor) { + clientHost.goToProjectConfig(isTypeScript, editor.document.uri, editor.document.languageId); + } + }; + context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true))); + context.subscriptions.push(commands.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false))); + window.onDidChangeActiveTextEditor(VersionStatus.showHideStatus, null, context.subscriptions); client.onReady().then(() => { context.subscriptions.push(ProjectStatus.create(client, @@ -378,6 +398,76 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { return !!this.findLanguage(file); } + public goToProjectConfig( + isTypeScriptProject: boolean, + resource: Uri, + languageId: string + ): Thenable | undefined { + const rootPath = workspace.rootPath; + if (!rootPath) { + window.showInformationMessage( + localize( + 'typescript.projectConfigNoWorkspace', + 'Please open a folder in VS Code to use a TypeScript or JavaScript project')); + return; + } + + const file = this.client.normalizePath(resource); + // TODO: TSServer errors when 'projectInfo' is invoked on a non js/ts file + if (!file || !this.languagePerId[languageId]) { + window.showWarningMessage( + localize( + 'typescript.projectConfigUnsupportedFile', + 'Could not determine TypeScript or JavaScript project. Unsupported file type')); + return; + } + + return this.client.execute('projectInfo', { file, needFileNameList: false }).then(res => { + if (!res || !res.body) { + return window.showWarningMessage(localize('typescript.projectConfigCouldNotGetInfo', 'Could not determine TypeScript or JavaScript project')); + } + + const { configFileName } = res.body; + if (configFileName && configFileName.indexOf('/dev/null/') !== 0) { + return workspace.openTextDocument(configFileName) + .then(window.showTextDocument); + } + + return window.showInformationMessage( + (isTypeScriptProject + ? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project') + : localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project') + ), { + title: isTypeScriptProject + ? localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json') + : localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json'), + id: ProjectConfigAction.CreateConfig + }, { + title: localize('typescript.projectConfigLearnMore', 'Learn More'), + id: ProjectConfigAction.LearnMore + }).then(selected => { + switch (selected && selected.id) { + case ProjectConfigAction.CreateConfig: + const configFile = Uri.file(path.join(rootPath, isTypeScriptProject ? 'tsconfig.json' : 'jsconfig.json')); + return workspace.openTextDocument(configFile) + .then(undefined, _ => workspace.openTextDocument(configFile.with({ scheme: 'untitled' }))) + .then(window.showTextDocument); + + case ProjectConfigAction.LearnMore: + if (isTypeScriptProject) { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=841896')); + } else { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=759670')); + } + return; + + default: + return Promise.resolve(undefined); + } + }); + }); + } + private findLanguage(file: string): LanguageProvider | null { for (let i = 0; i < this.languages.length; i++) { let language = this.languages[i]; From b9eab3261feb89ed7052aee0efa4ab7afa659b74 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Feb 2017 14:15:17 -0800 Subject: [PATCH 025/140] Clairfy references codelens string. Fixes #20393 --- extensions/typescript/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index d925d83f779..dcd896b2703 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -26,6 +26,6 @@ "javascript.validate.enable": "Enable/disable JavaScript validation.", "typescript.goToProjectConfig.title": "Go to Project Configuration", "javascript.goToProjectConfig.title": "Go to Project Configuration", - "typescript.referencesCodeLens.enabled": "Enable/disable the references code lens.", + "typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens", "typescript.selectTypeScriptVersion.title": "Select TypeScript Version" } \ No newline at end of file From 6b5c1437052dced71230006728ade3d5b6ea4996 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Feb 2017 14:17:27 -0800 Subject: [PATCH 026/140] Clarify markdown setting string. Fixes #20409 --- extensions/markdown/package.nls.json | 2 +- extensions/typescript/package.nls.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/markdown/package.nls.json b/extensions/markdown/package.nls.json index 45eecf6d299..253ab8910ef 100644 --- a/extensions/markdown/package.nls.json +++ b/extensions/markdown/package.nls.json @@ -1,5 +1,5 @@ { - "markdown.preview.doubleClickToSwitchToEditor.desc": "Enable double clicking in the markdown preview switches to the editor.", + "markdown.preview.doubleClickToSwitchToEditor.desc": "Double click in the markdown preview to switch to the editor.", "markdown.preview.fontFamily.desc": "Controls the font family used in the markdown preview.", "markdown.preview.fontSize.desc": "Controls the font size in pixels used in the markdown preview.", "markdown.preview.lineHeight.desc": "Controls the line height used in the markdown preview. This number is relative to the font size.", diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index dcd896b2703..c33a12dbc21 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -26,6 +26,6 @@ "javascript.validate.enable": "Enable/disable JavaScript validation.", "typescript.goToProjectConfig.title": "Go to Project Configuration", "javascript.goToProjectConfig.title": "Go to Project Configuration", - "typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens", + "typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens.", "typescript.selectTypeScriptVersion.title": "Select TypeScript Version" } \ No newline at end of file From e28dbc6d796a2787de361ff189895a818c01a0ec Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Feb 2017 14:34:37 -0800 Subject: [PATCH 027/140] Clarify typings Fixes #20309 --- .../typescript/src/features/completionItemProvider.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index d9aaa0cc7e1..8d8ea94e8ba 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -153,8 +153,12 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise { if (this.typingsStatus.isAcquiringTypings) { return Promise.reject({ - label: localize('acquiringTypingsLabel', 'Acquiring typings...'), - detail: localize('acquiringTypingsDetail', 'Acquiring typings definitions for IntelliSense.') + label: localize( + { key: 'acquiringTypingsLabel', comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'] }, + 'Acquiring typings...'), + detail: localize( + { key: 'acquiringTypingsDetail', comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'] }, + 'Acquiring typings definitions for IntelliSense.') }); } From 480eaa519ee745528128933c7d4da99ff091ba99 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Feb 2017 14:39:39 -0800 Subject: [PATCH 028/140] Activate ts when workspace contains js or ts config to get intellisense for these config files --- extensions/typescript/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 652728d8a17..cf6f687e6ed 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -35,7 +35,9 @@ "onCommand:javascript.reloadProjects", "onCommand:typescript.selectTypeScriptVersion", "onCommand:javascript.goToProjectConfig", - "onCommand:typescript.goToProjectConfig" + "onCommand:typescript.goToProjectConfig", + "workspaceContains:jsconfig.json", + "workspaceContains:tsconfig.json" ], "main": "./out/typescriptMain", "enableProposedApi": true, From 65fd0a111dc6de14937eba37d30b78f172fda245 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 14 Feb 2017 00:30:09 +0100 Subject: [PATCH 029/140] update node-debug --- build/gulpfile.vscode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 45cacdf9b58..0ed65a87ae4 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.10.7' }, + { name: 'ms-vscode.node-debug', version: '1.10.8' }, { name: 'ms-vscode.node-debug2', version: '1.9.13' } ]; From 942359990afa1bd46f5b920151645b8c116707cc Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 14 Feb 2017 00:35:45 +0100 Subject: [PATCH 030/140] update node-debug --- build/gulpfile.vscode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 0ed65a87ae4..d224fb80a04 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.10.8' }, + { name: 'ms-vscode.node-debug', version: '1.10.9' }, { name: 'ms-vscode.node-debug2', version: '1.9.13' } ]; From 05580737d884510eb07d72389cfcbb10a4d100b2 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 13 Feb 2017 15:51:49 -0800 Subject: [PATCH 031/140] Make search viewlet message shorter - takes up too much vertical space as is. Also change the string to make it less likely to wrap and take another line. --- .../parts/search/browser/media/searchviewlet.css | 5 +++++ src/vs/workbench/parts/search/browser/searchViewlet.ts | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index 18cb186877f..076e88be8e1 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -82,6 +82,7 @@ cursor: pointer; width: 16px; height: 13px; + z-index: 2; /* Force it above the search results message, which has a negative top margin */ } .hc-black .monaco-workbench .search-viewlet .query-details .more, @@ -143,6 +144,10 @@ font-weight: normal; } +.search-viewlet .messages { + margin-top: -5px; +} + .search-viewlet .message { padding-left: 22px; padding-right: 22px; diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 3458f87b123..8f7bfec499a 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -1185,13 +1185,13 @@ export class SearchViewlet extends Viewlet { private buildResultCountMessage(resultCount: number, fileCount: number): string { if (resultCount === 1 && fileCount === 1) { - return nls.localize('search.file.result', "Found {0} result in {1} file", resultCount, fileCount); + return nls.localize('search.file.result', "{0} result in {1} file", resultCount, fileCount); } else if (resultCount === 1) { - return nls.localize('search.files.result', "Found {0} result in {1} files", resultCount, fileCount); + return nls.localize('search.files.result', "{0} result in {1} files", resultCount, fileCount); } else if (fileCount === 1) { - return nls.localize('search.file.results', "Found {0} results in {1} file", resultCount, fileCount); + return nls.localize('search.file.results', "{0} results in {1} file", resultCount, fileCount); } else { - return nls.localize('search.files.results', "Found {0} results in {1} files", resultCount, fileCount); + return nls.localize('search.files.results', "{0} results in {1} files", resultCount, fileCount); } } From 154df1b46adfc0740bb533b156d561da95eb4dc9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 13 Feb 2017 15:56:44 -0800 Subject: [PATCH 032/140] Hopefully fix #20410 --- src/vs/workbench/parts/search/browser/media/searchviewlet.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index 076e88be8e1..ab1efadb6fb 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ .search-viewlet .search-widgets-container { - margin: 6px 9px 0 2px; + margin: 0px 9px 0 2px; + padding-top: 6px; } .search-viewlet .search-widget .toggle-replace-button { From 2c185a4fc5daee2e5df2bbe2605a2436ebe4e06c Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 13 Feb 2017 16:48:15 -0800 Subject: [PATCH 033/140] Add telemetry for clone action (fixes #20570) --- src/vs/base/common/actions.ts | 3 ++- src/vs/platform/telemetry/common/telemetry.ts | 7 ++++++- src/vs/platform/telemetry/common/telemetryService.ts | 4 ++-- src/vs/platform/telemetry/common/telemetryUtils.ts | 4 ++-- src/vs/workbench/common/actionRegistry.ts | 5 +++-- .../workbench/parts/git/electron-browser/gitActions.ts | 10 +++++++++- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 28b9b368c32..162172f2ad8 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -9,6 +9,7 @@ import { IEventEmitter, EventEmitter } from 'vs/base/common/eventEmitter'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as Events from 'vs/base/common/events'; import Event, { Emitter } from 'vs/base/common/event'; +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; export interface IAction extends IDisposable { id: string; @@ -202,7 +203,7 @@ export class Action implements IAction { this._order = value; } - public run(event?: any): TPromise { + public run(event?: any, data?: ITelemetryData): TPromise { if (this._actionCallback !== void 0) { return this._actionCallback(event); } diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index aca834c2d6f..f1959d63000 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -15,6 +15,11 @@ export interface ITelemetryInfo { instanceId: string; } +export interface ITelemetryData { + from?: string; + [key: string]: any; +} + export interface ITelemetryExperiments { showNewUserWatermark: boolean; openUntitledFile: boolean; @@ -30,7 +35,7 @@ export interface ITelemetryService { * Sends a telemetry event that has been privacy approved. * Do not call this unless you have been given approval. */ - publicLog(eventName: string, data?: any): TPromise; + publicLog(eventName: string, data?: ITelemetryData): TPromise; getTelemetryInfo(): TPromise; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 2b886246083..aa7e3f4a754 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; -import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryAppender, defaultExperiments } from 'vs/platform/telemetry/common/telemetryUtils'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -100,7 +100,7 @@ export class TelemetryService implements ITelemetryService { this._disposables = dispose(this._disposables); } - publicLog(eventName: string, data?: any): TPromise { + publicLog(eventName: string, data?: ITelemetryData): TPromise { // don't send events when the user is optout if (!this._userOptIn) { return TPromise.as(undefined); diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 8ef9acb59ef..10be3544472 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -14,7 +14,7 @@ import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/com import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StorageService } from 'vs/platform/storage/common/storageService'; import * as objects from 'vs/base/common/objects'; @@ -28,7 +28,7 @@ export const defaultExperiments: ITelemetryExperiments = { export const NullTelemetryService = { _serviceBrand: undefined, _experiments: defaultExperiments, - publicLog(eventName: string, data?: any) { + publicLog(eventName: string, data?: ITelemetryData) { return TPromise.as(null); }, isOptedIn: true, diff --git a/src/vs/workbench/common/actionRegistry.ts b/src/vs/workbench/common/actionRegistry.ts index e16dd395bcd..95669c4d86e 100644 --- a/src/vs/workbench/common/actionRegistry.ts +++ b/src/vs/workbench/common/actionRegistry.ts @@ -168,14 +168,15 @@ export function triggerAndDisposeAction(instantitationService: IInstantiationSer return undefined; } + const from = args && args.from || 'keybinding'; if (telemetryService) { - telemetryService.publicLog('workbenchActionExecuted', { id: actionInstance.id, from: args && args.from || 'keybinding' }); + telemetryService.publicLog('workbenchActionExecuted', { id: actionInstance.id, from }); } // run action when workbench is created return partService.joinCreation().then(() => { try { - return TPromise.as(actionInstance.run()).then(() => { + return TPromise.as(actionInstance.run(undefined, { from })).then(() => { actionInstance.dispose(); }, (err) => { actionInstance.dispose(); diff --git a/src/vs/workbench/parts/git/electron-browser/gitActions.ts b/src/vs/workbench/parts/git/electron-browser/gitActions.ts index 1a809c3fbab..b168acb47bd 100644 --- a/src/vs/workbench/parts/git/electron-browser/gitActions.ts +++ b/src/vs/workbench/parts/git/electron-browser/gitActions.ts @@ -16,6 +16,8 @@ import { IGitService } from 'vs/workbench/parts/git/common/git'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import * as url from 'url'; import { remote } from 'electron'; +import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; const dialog = remote.dialog; @@ -29,12 +31,13 @@ export class CloneAction extends Action { @IQuickOpenService private quickOpenService: IQuickOpenService, @IMessageService private messageService: IMessageService, @IWindowsService private windowsService: IWindowsService, + @ITelemetryService private telemetryService: ITelemetryService, @IWorkspaceContextService private workspaceService: IWorkspaceContextService ) { super(id, label); } - run(): TPromise { + run(event?: any, data?: ITelemetryData): TPromise { return this.quickOpenService.input({ prompt: localize('valid', "Provide a valid git repository URL"), placeHolder: localize('url', "Repository URL"), @@ -50,6 +53,7 @@ export class CloneAction extends Action { }) .then(url => { if (!url) { + this.telemetryService.publicLog('gitClone', { ...data, outcome: 'no_URL' }); return TPromise.as(null); } @@ -59,6 +63,7 @@ export class CloneAction extends Action { }); if (!result || result.length === 0) { + this.telemetryService.publicLog('gitClone', { ...data, outcome: 'no_directory' }); return TPromise.as(null); } @@ -69,14 +74,17 @@ export class CloneAction extends Action { const clone = always(this.gitService.clone(url, result[0]), () => promise.cancel()); return clone.then(path => { + this.telemetryService.publicLog('gitClone', { ...data, outcome: 'success' }); const forceNewWindow = this.workspaceService.hasWorkspace(); return this.windowsService.openWindow([path], { forceNewWindow, forceReuseWindow: !forceNewWindow }); }).then(null, e => { if (/already exists and is not an empty directory/.test(e.stderr || '')) { + this.telemetryService.publicLog('gitClone', { ...data, outcome: 'directory_not_empty' }); return TPromise.wrapError(localize('already exists', "Destination repository already exists, please pick another directory to clone to.")); } + this.telemetryService.publicLog('gitClone', { ...data, outcome: isPromiseCanceledError(e) ? 'canceled' : 'error' }); return TPromise.wrapError(e); }); }); From adf3a29deaebd3a68629b4671b204a60c71041bf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 Feb 2017 19:34:53 -0800 Subject: [PATCH 034/140] Uplevel xterm.js --- npm-shrinkwrap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 55dc1daaf66..5a439df62b1 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -432,7 +432,7 @@ "xterm": { "version": "2.2.3", "from": "Tyriar/xterm.js#vscode-release/1.10", - "resolved": "git+https://github.com/Tyriar/xterm.js.git#90cd66bf353b86ad52d7b650760d8d879dd1c7b8" + "resolved": "git+https://github.com/Tyriar/xterm.js.git#51e66b5fe88158aa22ad808071d4fc0f831f3cf6" }, "yauzl": { "version": "2.3.1", From 1a76f05c9d975a37930bb1f0f44a85088e48cee6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 Feb 2017 19:39:53 -0800 Subject: [PATCH 035/140] Uplevel xterm.js --- npm-shrinkwrap.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5a439df62b1..5328da5bca5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -430,9 +430,9 @@ "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.0.tgz" }, "xterm": { - "version": "2.2.3", + "version": "2.3.0", "from": "Tyriar/xterm.js#vscode-release/1.10", - "resolved": "git+https://github.com/Tyriar/xterm.js.git#51e66b5fe88158aa22ad808071d4fc0f831f3cf6" + "resolved": "git+https://github.com/Tyriar/xterm.js.git#5513303451202b0135601a2f026602ed391b3906" }, "yauzl": { "version": "2.3.1", From bc4d3de046cae97a3f1f6af213de0477755c7674 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 Feb 2017 20:26:52 -0800 Subject: [PATCH 036/140] Uplevel node-pty@0.6.2 --- npm-shrinkwrap.json | 17 ++++++----------- package.json | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 55dc1daaf66..9659a06a58c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -333,19 +333,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, "node-pty": { - "version": "0.6.1", - "from": "node-pty@0.6.1", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.6.1.tgz", + "version": "0.6.2", + "from": "node-pty@0.6.2", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.6.2.tgz", "dependencies": { - "extend": { - "version": "1.2.1", - "from": "extend@>=1.2.1 <1.3.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz" - }, "nan": { - "version": "2.2.1", - "from": "nan@2.2.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz" + "version": "2.5.0", + "from": "nan@2.5.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.5.0.tgz" } } }, diff --git a/package.json b/package.json index 97771c90bb0..f248ab56750 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "iconv-lite": "0.4.15", "minimist": "1.2.0", "native-keymap": "0.3.0", - "node-pty": "0.6.1", + "node-pty": "0.6.2", "semver": "4.3.6", "vscode-debugprotocol": "1.16.0", "vscode-textmate": "3.1.0", From 4ad4d9085c2c5835bcfe446a75d84668e8c48ab2 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Tue, 14 Feb 2017 08:17:16 +0100 Subject: [PATCH 037/140] Make more actions commands to ease keyboard shortcut assignment --- .../parts/tasks/common/taskConfiguration.ts | 19 +- .../parts/tasks/common/taskSystem.ts | 4 +- .../electron-browser/task.contribution.ts | 319 ++++++------------ 3 files changed, 113 insertions(+), 229 deletions(-) diff --git a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts index f99b4e2f51b..67a3fe2164d 100644 --- a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts @@ -61,9 +61,9 @@ export interface PlatformTaskDescription { export interface CommandBinding { /** - * The command Id the task is bound to. + * The command identifer the task is bound to. */ - commandId?: string; + identifier?: string; /** * The title to use @@ -652,7 +652,7 @@ namespace ProblemMatcherConverter { namespace CommandBinding { export function isEmpty(value: TaskSystem.CommandBinding): boolean { - return !value || value.commandId === void 0 && value.title === void 0 && value.category === void 0; + return !value || value.identifier === void 0 && value.title === void 0 && value.category === void 0; } export function from(this: void, binding: CommandBinding, context: ParseContext): TaskSystem.CommandBinding { @@ -660,19 +660,14 @@ namespace CommandBinding { return undefined; } - if (!Types.isString(binding.commandId)) { + if (!Types.isString(binding.identifier)) { context.validationStatus.state = ValidationState.Warning; - context.logger.log(nls.localize('noCommandId', 'Warning: a command binding must defined a commandId. Ignoring binding.')); - return undefined; - } - if (!Types.isString(binding.title)) { - context.validationStatus.state = ValidationState.Warning; - context.logger.log(nls.localize('noTitle', 'Warning: a command binding must defined a title. Ignoring binding.')); + context.logger.log(nls.localize('noCommandId', 'Warning: a command binding must defined an identifier. Ignoring binding.')); return undefined; } let result: TaskSystem.CommandBinding = { - commandId: binding.commandId, - title: binding.title + identifier: binding.identifier, + title: '' }; if (Types.isString(binding.category)) { result.category = binding.category; diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index 60f6b31f02d..a77a9514164 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -138,9 +138,9 @@ export interface CommandConfiguration { export interface CommandBinding { /** - * The command Id the task is bound to. + * The command identifier the task is bound to. */ - commandId: string; + identifier: string; /** * The title to use diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index a9dfddffd64..14eaebec35b 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -38,6 +38,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; + import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -76,116 +79,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' let $ = Builder.$; let tasksCategory = nls.localize('tasksCategory', "Tasks"); -class AbstractTaskAction extends Action { - - protected taskService: ITaskService; - protected telemetryService: ITelemetryService; - protected messageService: IMessageService; - protected contextService: IWorkspaceContextService; - - constructor(id: string, label: string, @ITaskService taskService: ITaskService, - @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, - @IWorkspaceContextService contextService: IWorkspaceContextService) { - - super(id, label); - this.taskService = taskService; - this.telemetryService = telemetryService; - this.messageService = messageService; - this.contextService = contextService; - } - - protected canRun(): boolean { - if (!this.contextService.hasWorkspace()) { - this.messageService.show(Severity.Info, nls.localize('AbstractTaskAction.noWorkspace', 'Tasks are only available on a workspace folder.')); - return false; - } - return true; - } -} - -class BuildAction extends AbstractTaskAction { - public static ID = 'workbench.action.tasks.build'; - public static TEXT = nls.localize('BuildAction.label', "Run Build Task"); - - constructor(id: string, label: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(id, label, taskService, telemetryService, messageService, contextService); - } - - public run(): TPromise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - return this.taskService.build(); - } -} - -class TestAction extends AbstractTaskAction { - public static ID = 'workbench.action.tasks.test'; - public static TEXT = nls.localize('TestAction.label', "Run Test Task"); - - constructor(id: string, label: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(id, label, taskService, telemetryService, messageService, contextService); - } - - public run(): TPromise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - return this.taskService.runTest(); - } -} - -class RebuildAction extends AbstractTaskAction { - public static ID = 'workbench.action.tasks.rebuild'; - public static TEXT = nls.localize('RebuildAction.label', 'Run Rebuild Task'); - - constructor(id: string, label: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(id, label, taskService, telemetryService, messageService, contextService); - } - - public run(): TPromise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - return this.taskService.rebuild(); - } -} - -class CleanAction extends AbstractTaskAction { - public static ID = 'workbench.action.tasks.clean'; - public static TEXT = nls.localize('CleanAction.label', 'Run Clean Task'); - - constructor(id: string, label: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(id, label, taskService, telemetryService, messageService, contextService); - } - - public run(): TPromise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - return this.taskService.clean(); - } -} - -class CommandAction extends AbstractTaskAction { - constructor(id: string, label: string, private taskId: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(id, label, taskService, telemetryService, messageService, contextService); - } - - public run(): TPromise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - return this.taskService.run(this.taskId); - } -} - abstract class OpenTaskConfigurationAction extends Action { private configurationService: IConfigurationService; @@ -354,91 +247,6 @@ class ViewTerminalAction extends Action { } } -class TerminateAction extends AbstractTaskAction { - public static ID = 'workbench.action.tasks.terminate'; - public static TEXT = nls.localize('TerminateAction.label', "Terminate Running Task"); - - constructor(id: string, label: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @ITerminalService private terminalService: ITerminalService - ) { - super(id, label, taskService, telemetryService, messageService, contextService); - } - - public run(): TPromise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - if (this.taskService.inTerminal()) { - this.messageService.show(Severity.Info, { - message: nls.localize('TerminateAction.terminalSystem', 'The tasks are executed in the integrated terminal. Use the terminal to manage the tasks.'), - actions: [new ViewTerminalAction(this.terminalService), new CloseMessageAction()] - }); - return undefined; - } else { - return this.taskService.isActive().then((active) => { - if (active) { - return this.taskService.terminate().then((response) => { - if (response.success) { - return undefined; - } else if (response.code && response.code === TerminateResponseCode.ProcessNotFound) { - this.messageService.show(Severity.Error, nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting VS Code might result in orphaned processes.')); - return undefined; - } else { - return Promise.wrapError(nls.localize('TerminateAction.failed', 'Failed to terminate running task')); - } - }); - } - return undefined; - }); - } - } -} - -class ShowLogAction extends AbstractTaskAction { - public static ID = 'workbench.action.tasks.showLog'; - public static TEXT = nls.localize('ShowLogAction.label', "Show Task Log"); - - private outputService: IOutputService; - - constructor(id: string, label: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @IOutputService outputService: IOutputService) { - - super(id, label, taskService, telemetryService, messageService, contextService); - this.outputService = outputService; - } - - public run(): TPromise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - return this.outputService.getChannel(TaskService.OutputChannelId).show(true); - } -} - -class RunTaskAction extends AbstractTaskAction { - - public static ID = 'workbench.action.tasks.runTask'; - public static TEXT = nls.localize('RunTaskAction.label', "Run Task"); - private quickOpenService: IQuickOpenService; - - constructor(id: string, label: string, @IQuickOpenService quickOpenService: IQuickOpenService, - @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(id, label, taskService, telemetryService, messageService, contextService); - this.quickOpenService = quickOpenService; - } - - public run(event?: any): Promise { - if (!this.canRun()) { - return TPromise.as(undefined); - } - this.quickOpenService.show('task '); - return TPromise.as(null); - } -} - class StatusBarItem implements IStatusbarItem { private panelService: IPanelService; @@ -737,18 +545,45 @@ class TaskService extends EventEmitter implements ITaskService { }); lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown())); + this.registerCommands(); + } + + + private registerCommands(): void { CommandsRegistry.registerCommand('workbench.action.tasks.runTask', (accessor, arg) => { - if (Types.isString(arg)) { - this.tasks().then(tasks => { - for (let task of tasks) { - if (task.name === arg) { - this.run(task.id); - } - } - }); - } else { - this.quickOpenService.show('task '); + this.runTaskCommand(accessor, arg); + }); + + CommandsRegistry.registerCommand('workbench.action.tasks.terminate', (accessor, arg) => { + this.runTerminateCommand(); + }); + + CommandsRegistry.registerCommand('workbench.action.tasks.showLog', () => { + if (!this.canRunCommand()) { + return; } + this.showOutput(); + }); + + CommandsRegistry.registerCommand('workbench.action.tasks.build', () => { + if (!this.canRunCommand()) { + return; + } + this.build(); + }); + + KeybindingsRegistry.registerKeybindingRule({ + id: 'workbench.action.tasks.build', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: undefined, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B + }); + + CommandsRegistry.registerCommand('workbench.action.tasks.test', () => { + if (!this.canRunCommand()) { + return; + } + this.runTest(); }); } @@ -1124,6 +959,7 @@ class TaskService extends EventEmitter implements ITaskService { return this.configureAction(); } } + private handleError(err: any): void { let showOutput = true; if (err instanceof TaskError) { @@ -1132,10 +968,12 @@ class TaskService extends EventEmitter implements ITaskService { let needsTerminate = buildError.code === TaskErrors.RunningTask; if (needsConfig || needsTerminate) { let closeAction = new CloseMessageAction(); - let action = needsConfig + let action: Action = needsConfig ? this.getConfigureAction(buildError.code) - : new TerminateAction(TerminateAction.ID, TerminateAction.TEXT, this, this.telemetryService, this.messageService, this.contextService, this.terminalService); - + : new Action( + 'workbench.action.tasks.terminate', + nls.localize('TerminateAction.label', "Terminate Running Task"), + undefined, true, () => { this.runTerminateCommand(); return TPromise.as(undefined); }); closeAction.closeFunction = this.messageService.show(buildError.severity, { message: buildError.message, actions: [action, closeAction] }); } else { this.messageService.show(buildError.severity, buildError.message); @@ -1152,20 +990,71 @@ class TaskService extends EventEmitter implements ITaskService { this.outputChannel.show(true); } } + + private canRunCommand(): boolean { + if (!this.contextService.hasWorkspace()) { + this.messageService.show(Severity.Info, nls.localize('TaskService.noWorkspace', 'Tasks are only available on a workspace folder.')); + return false; + } + return true; + } + + private runTaskCommand(accessor: ServicesAccessor, arg: any): void { + if (!this.canRunCommand()) { + return; + } + if (Types.isString(arg)) { + this.tasks().then(tasks => { + for (let task of tasks) { + if (task.name === arg || (task.bindTo && task.bindTo.identifier === arg)) { + this.run(task.id); + } + } + }); + } else { + this.quickOpenService.show('task '); + } + } + + private runTerminateCommand(): void { + if (!this.canRunCommand()) { + return; + } + if (this.inTerminal()) { + this.messageService.show(Severity.Info, { + message: nls.localize('TerminateAction.terminalSystem', 'The tasks are executed in the integrated terminal. Use the terminal to manage the tasks.'), + actions: [new ViewTerminalAction(this.terminalService), new CloseMessageAction()] + }); + } else { + this.isActive().then((active) => { + if (active) { + this.terminate().then((response) => { + if (response.success) { + return undefined; + } else if (response.code && response.code === TerminateResponseCode.ProcessNotFound) { + this.messageService.show(Severity.Error, nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting VS Code might result in orphaned processes.')); + return undefined; + } else { + return Promise.wrapError(nls.localize('TerminateAction.failed', 'Failed to terminate running task')); + } + }); + } + }); + } + } } let workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureTaskRunnerAction, ConfigureTaskRunnerAction.ID, ConfigureTaskRunnerAction.TEXT), 'Tasks: Configure Task Runner', tasksCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(BuildAction, BuildAction.ID, BuildAction.TEXT, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B }), 'Tasks: Run Build Task', tasksCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(TestAction, TestAction.ID, TestAction.TEXT), 'Tasks: Run Test Task', tasksCategory); -// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(RebuildAction, RebuildAction.ID, RebuildAction.TEXT), tasksCategory); -// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CleanAction, CleanAction.ID, CleanAction.TEXT), tasksCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminateAction, TerminateAction.ID, TerminateAction.TEXT), 'Tasks: Terminate Running Task', tasksCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogAction, ShowLogAction.ID, ShowLogAction.TEXT), 'Tasks: Show Task Log', tasksCategory); -// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunTaskAction, RunTaskAction.ID, RunTaskAction.TEXT), 'Tasks: Run Task', tasksCategory); +MenuRegistry.addCommand({ id: 'workbench.action.tasks.showLog', title: nls.localize('ShowLogAction.label', "Show Task Log"), category: tasksCategory }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.runTask', title: nls.localize('RunTaskAction.label', "Run Task"), category: tasksCategory }); +MenuRegistry.addCommand({ id: 'workbench.action.tasks.terminate', title: nls.localize('TerminateAction.label', "Terminate Running Task"), category: tasksCategory }); +MenuRegistry.addCommand({ id: 'workbench.action.tasks.build', title: nls.localize('BuildAction.label', "Run Build Task"), category: tasksCategory }); +MenuRegistry.addCommand({ id: 'workbench.action.tasks.test', title: nls.localize('TestAction.label', "Run Test Task"), category: tasksCategory }); +// MenuRegistry.addCommand( { id: 'workbench.action.tasks.rebuild', title: nls.localize('RebuildAction.label', 'Run Rebuild Task'), category: tasksCategory }); +// MenuRegistry.addCommand( { id: 'workbench.action.tasks.clean', title: nls.localize('CleanAction.label', 'Run Clean Task'), category: tasksCategory }); // Task Service registerSingleton(ITaskService, TaskService); From 6bab39396271f72b573127564696e5fb73cbe050 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 13 Feb 2017 16:52:35 +0100 Subject: [PATCH 038/140] list: cleanup --- src/vs/base/browser/ui/list/listWidget.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 7e6fe181712..76b1d60c080 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -235,8 +235,6 @@ export class List implements IDisposable { private selection: Trait; private eventBufferer: EventBufferer; private view: ListView; - private mouseController: MouseController; - private keyboardController: KeyboardController; private disposables: IDisposable[]; @memoize @@ -290,13 +288,11 @@ export class List implements IDisposable { this.disposables.push(tracker.addBlurListener(() => this._onDOMBlur.fire())); if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) { - this.keyboardController = new KeyboardController(this, this.view); - this.disposables.push(this.keyboardController); + this.disposables.push(new KeyboardController(this, this.view)); } if (typeof options.mouseSupport !== 'boolean' || options.mouseSupport) { - this.mouseController = new MouseController(this, this.view); - this.disposables.push(this.mouseController); + this.disposables.push(new MouseController(this, this.view)); } this.onFocusChange(this._onFocusChange, this, this.disposables); From 1d17b45a11d8db22fe95c601f793a3cb9a582ba6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 14 Feb 2017 10:36:59 +0100 Subject: [PATCH 039/140] shorter IPC paths fixes #20567 --- .../environment/node/environmentService.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 4f2dab386eb..b8c47e548ea 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -29,25 +29,25 @@ function getUniqueUserId(): string { return crypto.createHash('sha256').update(username).digest('hex').substr(0, 6); } -function getIPCHandlePrefix(nixBaseDir: string = os.tmpdir()): string { - let name = pkg.name; - - // Support to run VS Code multiple times as different user - // by making the socket unique over the logged in user - let userId = getUniqueUserId(); - if (userId) { - name += `-${userId}`; - } - - if (process.platform === 'win32') { - return `\\\\.\\pipe\\${name}`; - } else { - return path.join(nixBaseDir, name); - } +function getNixIPCHandle(userDataPath: string, type: string): string { + return path.join(userDataPath, `${pkg.version}-${type}.sock`); } -function getIPCHandleSuffix(): string { - return process.platform === 'win32' ? '-sock' : '.sock'; +function getWin32IPCHandle(type: string): string { + // Support to run VS Code multiple times as different user + // by making the socket unique over the logged in user + const userId = getUniqueUserId(); + const name = product.applicationName + (userId ? `-${userId}` : ''); + + return `\\\\.\\pipe\\${name}-${pkg.version}-${type}-sock`; +} + +function getIPCHandle(userDataPath: string, type: string): string { + if (process.platform === 'win32') { + return getWin32IPCHandle(type); + } else { + return getNixIPCHandle(userDataPath, type); + } } export class EnvironmentService implements IEnvironmentService { @@ -113,10 +113,10 @@ export class EnvironmentService implements IEnvironmentService { get logExtensionHostCommunication(): boolean { return this._args.logExtensionHostCommunication; } @memoize - get mainIPCHandle(): string { return `${getIPCHandlePrefix(this.userDataPath)}-${pkg.version}${getIPCHandleSuffix()}`; } + get mainIPCHandle(): string { return getIPCHandle(this.userDataPath, 'main'); } @memoize - get sharedIPCHandle(): string { return `${getIPCHandlePrefix(this.userDataPath)}-${pkg.version}-shared${getIPCHandleSuffix()}`; } + get sharedIPCHandle(): string { return getIPCHandle(this.userDataPath, 'shared'); } @memoize get nodeCachedDataDir(): string { return path.join(this.userDataPath, 'CachedData'); } From be93e8fca23062709ce32fe9659fde99ce062456 Mon Sep 17 00:00:00 2001 From: Michel Kaporin Date: Tue, 14 Feb 2017 10:53:00 +0100 Subject: [PATCH 040/140] Colour for column selection in debugging mode added to high contrast theme. Amended yellow frame column selection to stand out in light themes. --- .../parts/debug/browser/media/debug.contribution.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css index de5b96b7d09..a080daf44fc 100644 --- a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css @@ -17,7 +17,7 @@ } .monaco-editor .debug-top-stack-frame-column { - background: rgba(255, 200, 0, 0.2); + background: rgba(255, 200, 0, 0.7); } .monaco-editor .debug-focused-stack-frame-line { @@ -244,4 +244,8 @@ .hc-black .monaco-editor .debug-top-stack-frame-line { background: rgba(255, 246, 0, .87); mix-blend-mode: lighten; /* Preserves text selection's contrast */ +} + +.hc-black .monaco-editor .debug-top-stack-frame-column { + outline: 2px solid #ff0000; } \ No newline at end of file From 35b261cb9f05388d75b5a0268060d31cf28fa3af Mon Sep 17 00:00:00 2001 From: Michel Kaporin Date: Tue, 14 Feb 2017 11:49:53 +0100 Subject: [PATCH 041/140] Amended current pointer background colour to the same as the line highlight. --- .../parts/debug/browser/media/current-and-breakpoint.svg | 2 +- src/vs/workbench/parts/debug/browser/media/current-arrow.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg b/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg index 1635dcb16f4..28c93fc2a92 100644 --- a/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg +++ b/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/media/current-arrow.svg b/src/vs/workbench/parts/debug/browser/media/current-arrow.svg index bd0d7541109..339d5826e8b 100644 --- a/src/vs/workbench/parts/debug/browser/media/current-arrow.svg +++ b/src/vs/workbench/parts/debug/browser/media/current-arrow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 1f22048fc2bf3130d4331ca6173f326131714db6 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 13 Feb 2017 17:54:17 +0100 Subject: [PATCH 042/140] debug: add column breakpoint action in context menu --- .../parts/debug/browser/debugEditorActions.ts | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts index 798a6ba7487..9a7b7751dba 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts @@ -48,6 +48,24 @@ class ToggleBreakpointAction extends EditorAction { } } +function addColumnBreakpoint(accessor: ServicesAccessor, editor: ICommonCodeEditor, remove: boolean): TPromise { + const debugService = accessor.get(IDebugService); + + const position = editor.getPosition(); + const modelUri = editor.getModel().uri; + const bp = debugService.getModel().getBreakpoints() + .filter(bp => bp.lineNumber === position.lineNumber && bp.column === position.column && bp.uri.toString() === modelUri.toString()).pop(); + + if (bp) { + return remove ? debugService.removeBreakpoints(bp.getId()) : TPromise.as(null); + } + if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { + return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column }]); + } + + return TPromise.as(null); +} + @editorAction class ToggleColumnBreakpointAction extends EditorAction { constructor() { @@ -64,26 +82,33 @@ class ToggleColumnBreakpointAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): TPromise { - const debugService = accessor.get(IDebugService); + return addColumnBreakpoint(accessor, editor, true); + } +} - const position = editor.getPosition(); - const modelUri = editor.getModel().uri; - const bp = debugService.getModel().getBreakpoints() - .filter(bp => bp.lineNumber === position.lineNumber && bp.column === position.column && bp.uri.toString() === modelUri.toString()).pop(); +// TODO@Isidor merge two column breakpoints actions together +@editorAction +class ToggleColumnBreakpointContextMenuAction extends EditorAction { + constructor() { + super({ + id: 'editor.debug.action.toggleColumnBreakpointContextMenu', + label: nls.localize('columnBreakpoint', "Add Column Breakpoint"), + alias: 'Toggle Column Breakpoint', + precondition: CONTEXT_IN_DEBUG_MODE, + menuOpts: { + group: 'debug', + order: 1 + } + }); + } - if (bp) { - return debugService.removeBreakpoints(bp.getId()); - } - if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column }]); - } - - return TPromise.as(null); + public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): TPromise { + return addColumnBreakpoint(accessor, editor, false); } } @editorAction -class EditorConditionalBreakpointAction extends EditorAction { +class ConditionalBreakpointAction extends EditorAction { constructor() { super({ @@ -111,7 +136,7 @@ class RunToCursorAction extends EditorAction { constructor() { super({ id: 'editor.debug.action.runToCursor', - label: nls.localize('runToCursor', "Debug: Run to Cursor"), + label: nls.localize('runToCursor', "Run to Cursor"), alias: 'Debug: Run to Cursor', precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, EditorContextKeys.Writable), menuOpts: { From 35a53029928d8ccea82994d86483f4901e073901 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 14 Feb 2017 12:40:11 +0100 Subject: [PATCH 043/140] debug: move clearing of debug repl out of createProcess --- src/vs/workbench/parts/debug/browser/debugActions.ts | 6 ++++++ .../workbench/parts/debug/electron-browser/debugService.ts | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 883004aadfb..9582ba526f3 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -127,6 +127,9 @@ export class StartAction extends AbstractDebugAction { public run(): TPromise { return this.commandService.executeCommand('workbench.action.files.save').then(() => { + if (this.debugService.getModel().getProcesses().length === 0) { + this.debugService.removeReplExpressions(); + } const manager = this.debugService.getConfigurationManager(); const configName = this.debugService.getViewModel().selectedConfigurationName; const compound = manager.getCompound(configName); @@ -227,6 +230,9 @@ export class RestartAction extends AbstractDebugAction { process = this.debugService.getViewModel().focusedProcess; } + if (this.debugService.getModel().getProcesses().length <= 1) { + this.debugService.removeReplExpressions(); + } return this.debugService.restartProcess(process); } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 7f0376cfbcd..2744fdb0bc6 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -559,11 +559,6 @@ export class DebugService implements debug.IDebugService { } public createProcess(configurationOrName: debug.IConfig | string): TPromise { - if (this.model.getProcesses().length === 0) { - // Repl shouldn't be cleared if a process is already running since the repl is shared. - this.removeReplExpressions(); - } - const sessionId = generateUuid(); this.setStateAndEmit(sessionId, debug.State.Initializing); From 8dde28b80bc41f91cb4e5141946f65538f92db74 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 14 Feb 2017 12:57:15 +0100 Subject: [PATCH 044/140] debug: use warning as severity to get the orange color for messages coming from the debug extension --- src/vs/workbench/parts/debug/common/debug.ts | 3 ++- .../workbench/parts/debug/electron-browser/debugCommands.ts | 4 +++- src/vs/workbench/parts/debug/electron-browser/debugService.ts | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index f25922f6fe1..471c70d0ce4 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -5,6 +5,7 @@ import uri from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; +import severity from 'vs/base/common/severity'; import Event from 'vs/base/common/event'; import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -463,7 +464,7 @@ export interface IDebugService { /** * Appends the passed string to the debug repl. */ - logToRepl(value: string): void; + logToRepl(value: string, sev?: severity): void; /** * Adds a new watch expression and evaluates it against the debug adapter. diff --git a/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts b/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts index f9275e6c237..eadd61d2d25 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import severity from 'vs/base/common/severity'; import { List } from 'vs/base/browser/ui/list/listWidget'; import * as errors from 'vs/base/common/errors'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -48,7 +49,8 @@ export function registerCommands(): void { handler(accessor: ServicesAccessor, value: string) { if (typeof value === 'string') { const debugService = accessor.get(IDebugService); - debugService.logToRepl(value); + // Use warning as severity to get the orange color for messages coming from the debug extension + debugService.logToRepl(value, severity.Warning); } }, when: undefined, diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 2744fdb0bc6..31d8081ee7a 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -536,8 +536,8 @@ export class DebugService implements debug.IDebugService { this.model.removeReplExpressions(); } - public logToRepl(value: string): void { - this.model.appendToRepl(value, severity.Info); + public logToRepl(value: string, sev = severity.Info): void { + this.model.appendToRepl(value, sev); } public addWatchExpression(name: string): TPromise { From 7d2bf6acaace0f3469a814ce35fd6e12221a9af9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 14 Feb 2017 16:22:37 +0100 Subject: [PATCH 045/140] list: support trait persistence --- src/vs/base/browser/ui/list/listPaging.ts | 2 +- src/vs/base/browser/ui/list/listWidget.ts | 57 ++++++++++++++--------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index b5649f39e12..33347b680ca 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -67,7 +67,7 @@ export class PagedList { container: HTMLElement, delegate: IDelegate, renderers: IPagedRenderer[], - options: IListOptions = {} + options: IListOptions = {} // TODO@Joao: should be IListOptions ) { const pagedRenderers = renderers.map(r => new PagedRenderer>(r, () => this.model)); this.list = new List(container, delegate, pagedRenderers, options); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 76b1d60c080..80726c6ff8f 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -16,6 +16,10 @@ import { domEvent } from 'vs/base/browser/event'; import { IDelegate, IRenderer, IListMouseEvent, IFocusChangeEvent, ISelectionChangeEvent } from './list'; import { ListView, IListViewOptions } from './listView'; +export interface IIdentityProvider { + (element: T): string; +} + interface ITraitTemplateData { container: HTMLElement; data: D; @@ -63,18 +67,14 @@ class Trait implements IDisposable { this.indexes = []; } - splice(start: number, deleteCount: number, insertCount: number): void { - const diff = insertCount - deleteCount; + splice(start: number, deleteCount: number, elements: boolean[]): void { + const diff = elements.length - deleteCount; const end = start + deleteCount; - const indexes: number[] = []; - - for (let index of indexes) { - if (index >= start && index < end) { - continue; - } - - indexes.push(index > start ? index + diff : index); - } + const indexes = [ + ...this.indexes.filter(i => i < start), + ...elements.reduce((r, hasTrait, i) => hasTrait ? [...r, i + start] : r, []), + ...this.indexes.filter(i => i >= end).map(i => i + diff) + ]; this.indexes = indexes; this._onChange.fire({ indexes }); @@ -111,14 +111,16 @@ class Trait implements IDisposable { class FocusTrait extends Trait { - constructor(private getElementId: (number: number) => string) { + constructor( + private getDomId: IIdentityProvider + ) { super('focused'); } renderElement(element: T, index: number, container: HTMLElement): void { super.renderElement(element, index, container); container.setAttribute('role', 'treeitem'); - container.setAttribute('id', this.getElementId(index)); + container.setAttribute('id', this.getDomId(index)); } } @@ -215,13 +217,14 @@ class MouseController implements IDisposable { } } -export interface IListOptions extends IListViewOptions { +export interface IListOptions extends IListViewOptions { + identityProvider?: IIdentityProvider; ariaLabel?: string; mouseSupport?: boolean; keyboardSupport?: boolean; } -const DefaultOptions: IListOptions = { +const DefaultOptions: IListOptions = { keyboardSupport: true, mouseSupport: true }; @@ -235,16 +238,17 @@ export class List implements IDisposable { private selection: Trait; private eventBufferer: EventBufferer; private view: ListView; + private getId?: IIdentityProvider; private disposables: IDisposable[]; @memoize get onFocusChange(): Event> { - return this.eventBufferer.wrapEvent(mapEvent(this.focus.onChange, e => this.toListEvent(e))); + return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); } @memoize get onSelectionChange(): Event> { - return this.eventBufferer.wrapEvent(mapEvent(this.selection.onChange, e => this.toListEvent(e))); + return mapEvent(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e)); } @memoize @@ -265,11 +269,12 @@ export class List implements IDisposable { container: HTMLElement, delegate: IDelegate, renderers: IRenderer[], - options: IListOptions = DefaultOptions + options: IListOptions = DefaultOptions ) { - this.focus = new FocusTrait(i => this.getElementId(i)); + this.focus = new FocusTrait(i => this.getElementDomId(i)); this.selection = new Trait('selected'); this.eventBufferer = new EventBufferer(); + this.getId = options.identityProvider; renderers = renderers.map(r => { r = this.focus.wrapRenderer(r); @@ -304,8 +309,14 @@ export class List implements IDisposable { splice(start: number, deleteCount: number, elements: T[] = []): void { this.eventBufferer.bufferEvents(() => { - this.focus.splice(start, deleteCount, elements.length); - this.selection.splice(start, deleteCount, elements.length); + const focus = this.focus.get().map(i => this.getId(this.view.element(i))); + const focusElements = elements.map(e => focus.indexOf(this.getId(e)) > -1); + + const selection = this.selection.get().map(i => this.getId(this.view.element(i))); + const selectionElements = elements.map(e => selection.indexOf(this.getId(e)) > -1); + + this.focus.splice(start, deleteCount, focusElements); + this.selection.splice(start, deleteCount, selectionElements); this.view.splice(start, deleteCount, elements); }); } @@ -457,7 +468,7 @@ export class List implements IDisposable { } } - getElementId(index: number): string { + getElementDomId(index: number): string { return `${this.idPrefix}_${index}`; } @@ -477,7 +488,7 @@ export class List implements IDisposable { const focus = this.focus.get(); if (focus.length > 0) { - this.view.domNode.setAttribute('aria-activedescendant', this.getElementId(focus[0])); + this.view.domNode.setAttribute('aria-activedescendant', this.getElementDomId(focus[0])); } else { this.view.domNode.removeAttribute('aria-activedescendant'); } From efbd7530e66cddd0db5b6ccf192fa92b8a9eb38a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 14 Feb 2017 16:22:47 +0100 Subject: [PATCH 046/140] scm: use list persistence fixes #18672 --- .../parts/scm/electron-browser/scmViewlet.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index b274b4414e3..cf2e6d457af 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -35,6 +35,18 @@ import { isDarkTheme } from 'vs/platform/theme/common/themes'; import { SCMEditor } from './scmEditor'; import { IModelService } from 'vs/editor/common/services/modelService'; +function isSCMResource(element: ISCMResourceGroup | ISCMResource): element is ISCMResource { + return !!(element as ISCMResource).uri; +} + +function getElementId(element: ISCMResourceGroup | ISCMResource) { + if (isSCMResource(element)) { + return `${element.resourceGroupId}:${element.uri.toString()}`; + } else { + return `${element.id}`; + } +} + interface SearchInputEvent extends Event { target: HTMLInputElement; immediate?: boolean; @@ -135,7 +147,7 @@ class Delegate implements IDelegate { getHeight() { return 22; } getTemplateId(element: ISCMResourceGroup | ISCMResource) { - return (element as ISCMResource).uri ? ResourceRenderer.TEMPLATE_ID : ResourceGroupRenderer.TEMPLATE_ID; + return isSCMResource(element) ? ResourceRenderer.TEMPLATE_ID : ResourceGroupRenderer.TEMPLATE_ID; } } @@ -198,17 +210,21 @@ export class SCMViewlet extends Viewlet { const delegate = new Delegate(); const actionItemProvider = action => this.getActionItem(action); - - this.list = new List(this.listContainer, delegate, [ + const renderers = [ new ResourceGroupRenderer(this.menus, actionItemProvider), this.instantiationService.createInstance(ResourceRenderer, this.menus, actionItemProvider), - ], { keyboardSupport: false }); + ]; + + this.list = new List(this.listContainer, delegate, renderers, { + identityProvider: e => getElementId(e), + keyboardSupport: false + }); this.disposables.push(this.listService.register(this.list)); chain(this.list.onSelectionChange) .map(e => e.elements[0]) - .filter(e => !!e && !!(e as ISCMResource).uri) + .filter(e => !!e && isSCMResource(e)) .on(this.open, this, this.disposables); this.list.onContextMenu(this.onListContextMenu, this, this.disposables); @@ -279,12 +295,10 @@ export class SCMViewlet extends Viewlet { const element = e.element; let actions: IAction[]; - if ((element as ISCMResource).uri) { - const resource = element as ISCMResource; - actions = this.menus.getResourceContextActions(resource); + if (isSCMResource(element)) { + actions = this.menus.getResourceContextActions(element); } else { - const resourceGroup = element as ISCMResourceGroup; - actions = this.menus.getResourceGroupContextActions(resourceGroup); + actions = this.menus.getResourceGroupContextActions(element); } this.contextMenuService.showContextMenu({ From 62383be39eefb80cf70786d616339dd68632afee Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 14 Feb 2017 16:47:44 +0100 Subject: [PATCH 047/140] list: cleanup trait preserving splice --- src/vs/base/browser/ui/list/listWidget.ts | 63 +++++++++++++++++------ 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 80726c6ff8f..e329c16252c 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -20,6 +20,19 @@ export interface IIdentityProvider { (element: T): string; } +export interface ISpliceable { + splice(start: number, deleteCount: number, elements: T[]): void; +} + +class CombinedSpliceable implements ISpliceable { + + constructor(private spliceables: ISpliceable[]) { } + + splice(start: number, deleteCount: number, elements: T[]): void { + this.spliceables.forEach(s => s.splice(start, deleteCount, elements)); + } +} + interface ITraitTemplateData { container: HTMLElement; data: D; @@ -56,7 +69,7 @@ class TraitRenderer implements IRenderer> } } -class Trait implements IDisposable { +class Trait implements ISpliceable, IDisposable { private indexes: number[]; @@ -124,6 +137,31 @@ class FocusTrait extends Trait { } } +/** + * The TraitSpliceable is used as a util class to be able + * to preserve traits across splice calls, given an identity + * provider. + */ +class TraitSpliceable implements ISpliceable { + + constructor( + private trait: Trait, + private view: ListView, + private getId?: IIdentityProvider + ) { } + + splice(start: number, deleteCount: number, elements: T[]): void { + if (!this.getId) { + return this.trait.splice(start, deleteCount, elements.map(e => false)); + } + + const pastElementsWithTrait = this.trait.get().map(i => this.getId(this.view.element(i))); + const elementsWithTrait = elements.map(e => pastElementsWithTrait.indexOf(this.getId(e)) > -1); + + this.trait.splice(start, deleteCount, elementsWithTrait); + } +} + class KeyboardController implements IDisposable { private disposables: IDisposable[]; @@ -229,7 +267,7 @@ const DefaultOptions: IListOptions = { mouseSupport: true }; -export class List implements IDisposable { +export class List implements ISpliceable, IDisposable { private static InstanceCount = 0; private idPrefix = `list_id_${++List.InstanceCount}`; @@ -238,7 +276,7 @@ export class List implements IDisposable { private selection: Trait; private eventBufferer: EventBufferer; private view: ListView; - private getId?: IIdentityProvider; + private spliceable: ISpliceable; private disposables: IDisposable[]; @memoize @@ -274,7 +312,6 @@ export class List implements IDisposable { this.focus = new FocusTrait(i => this.getElementDomId(i)); this.selection = new Trait('selected'); this.eventBufferer = new EventBufferer(); - this.getId = options.identityProvider; renderers = renderers.map(r => { r = this.focus.wrapRenderer(r); @@ -286,6 +323,12 @@ export class List implements IDisposable { this.view.domNode.setAttribute('role', 'tree'); this.view.domNode.tabIndex = 0; + this.spliceable = new CombinedSpliceable([ + new TraitSpliceable(this.focus, this.view, options.identityProvider), + new TraitSpliceable(this.selection, this.view, options.identityProvider), + this.view + ]); + this.disposables = [this.focus, this.selection, this.view, this._onDispose]; const tracker = DOM.trackFocus(this.view.domNode); @@ -308,17 +351,7 @@ export class List implements IDisposable { } splice(start: number, deleteCount: number, elements: T[] = []): void { - this.eventBufferer.bufferEvents(() => { - const focus = this.focus.get().map(i => this.getId(this.view.element(i))); - const focusElements = elements.map(e => focus.indexOf(this.getId(e)) > -1); - - const selection = this.selection.get().map(i => this.getId(this.view.element(i))); - const selectionElements = elements.map(e => selection.indexOf(this.getId(e)) > -1); - - this.focus.splice(start, deleteCount, focusElements); - this.selection.splice(start, deleteCount, selectionElements); - this.view.splice(start, deleteCount, elements); - }); + this.eventBufferer.bufferEvents(() => this.spliceable.splice(start, deleteCount, elements)); } get length(): number { From e1ef2a9300d558e03e2aeeef75579fc43ad14787 Mon Sep 17 00:00:00 2001 From: Michel Kaporin Date: Tue, 14 Feb 2017 18:17:47 +0100 Subject: [PATCH 048/140] Line and column highlight colours amended after review for different themes. --- .../browser/media/current-and-breakpoint.svg | 2 +- .../parts/debug/browser/media/current-arrow.svg | 2 +- .../debug/browser/media/debug.contribution.css | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg b/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg index 28c93fc2a92..131f418672b 100644 --- a/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg +++ b/src/vs/workbench/parts/debug/browser/media/current-and-breakpoint.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/media/current-arrow.svg b/src/vs/workbench/parts/debug/browser/media/current-arrow.svg index 339d5826e8b..69361d36d1b 100644 --- a/src/vs/workbench/parts/debug/browser/media/current-arrow.svg +++ b/src/vs/workbench/parts/debug/browser/media/current-arrow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css index a080daf44fc..1a062e81750 100644 --- a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css @@ -9,7 +9,7 @@ } .monaco-editor .debug-top-stack-frame-line { - background: rgba(255, 255, 0, 0.35); + background: rgba(255, 255, 102, 0.25); } .monaco-editor .debug-top-stack-frame-exception-line { @@ -17,7 +17,7 @@ } .monaco-editor .debug-top-stack-frame-column { - background: rgba(255, 200, 0, 0.7); + background: rgba(255, 255, 102, 0.75); } .monaco-editor .debug-focused-stack-frame-line { @@ -181,7 +181,7 @@ } .monaco-editor.vs-dark .debug-top-stack-frame-line { - background: rgba(255, 255, 0, 0.20); + background: rgba(255, 255, 0, 0.15) } .monaco-editor.vs-dark .debug-top-stack-frame-exception-line { @@ -189,7 +189,7 @@ } .monaco-editor.vs-dark .debug-top-stack-frame-column { - background: rgba(255, 255, 0, 0.15); + background: rgba(255, 255, 0, 0.25) } .monaco-editor.vs-dark .debug-breakpoint-glyph, @@ -242,10 +242,11 @@ } .hc-black .monaco-editor .debug-top-stack-frame-line { - background: rgba(255, 246, 0, .87); + background: rgba(255, 255, 0, .87); mix-blend-mode: lighten; /* Preserves text selection's contrast */ } .hc-black .monaco-editor .debug-top-stack-frame-column { - outline: 2px solid #ff0000; -} \ No newline at end of file + background: none; + outline: 2px solid #f38518; +} From 2d0199105f2ad41c17b58885909bb400a956ae5b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 14 Feb 2017 13:35:15 -0800 Subject: [PATCH 049/140] Fix #20617 --- src/vs/workbench/parts/search/browser/media/searchviewlet.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index ab1efadb6fb..c26fe1c07d8 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -147,6 +147,7 @@ .search-viewlet .messages { margin-top: -5px; + cursor: default; } .search-viewlet .message { From ac5beb61b230f37341791f328cfe9bc938f20a3f Mon Sep 17 00:00:00 2001 From: Kazuyuki Sato Date: Wed, 15 Feb 2017 09:50:20 +0900 Subject: [PATCH 050/140] markdown-it-named-header custom slugify for non-latin characters (#20628) * markdown-it-named-header custom slugify for non-latin characters * Delete comment --- extensions/markdown/src/markdownEngine.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions/markdown/src/markdownEngine.ts b/extensions/markdown/src/markdownEngine.ts index 6e878f554aa..0a699fef56d 100644 --- a/extensions/markdown/src/markdownEngine.ts +++ b/extensions/markdown/src/markdownEngine.ts @@ -44,7 +44,15 @@ export class MarkdownEngine { } return `
    ${this.engine.utils.escapeHtml(str)}
    `; } - }).use(mdnh, {}); + }).use(mdnh, { + slugify: function (header: string) { + return encodeURI(header.trim() + .toLowerCase() + .replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '') + .replace(/\s+/g, '-') + .replace(/\-+$/, '')); + } + }); for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'blockquote_open', 'list_item_open']) { this.addLineNumberRenderer(this.md, renderName); From 4f6efb0ef5f6d40ced3d12e3c889217c46e77473 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 14 Feb 2017 17:07:38 -0800 Subject: [PATCH 051/140] Use same slugify logic for editor links as well --- extensions/markdown/src/documentSymbolProvider.ts | 4 ++-- extensions/markdown/src/extension.ts | 4 ++-- extensions/markdown/src/markdownEngine.ts | 9 ++------- extensions/markdown/src/tableOfContentsProvider.ts | 14 ++++++++------ 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/extensions/markdown/src/documentSymbolProvider.ts b/extensions/markdown/src/documentSymbolProvider.ts index c3af584ba1f..207fabb2cc7 100644 --- a/extensions/markdown/src/documentSymbolProvider.ts +++ b/extensions/markdown/src/documentSymbolProvider.ts @@ -8,14 +8,14 @@ import * as vscode from 'vscode'; import { MarkdownEngine } from './markdownEngine'; -import { TableOfContentProvider } from './tableOfContentsProvider'; +import { TableOfContentsProvider } from './tableOfContentsProvider'; export default class MDDocumentSymbolProvider implements vscode.DocumentSymbolProvider { constructor(private engine: MarkdownEngine) { } provideDocumentSymbols(document: vscode.TextDocument): vscode.ProviderResult { - const toc = new TableOfContentProvider(this.engine, document); + const toc = new TableOfContentsProvider(this.engine, document); return toc.getToc().map(entry => { return new vscode.SymbolInformation(entry.text, vscode.SymbolKind.Module, '', entry.location); }); diff --git a/extensions/markdown/src/extension.ts b/extensions/markdown/src/extension.ts index 9d660ba1238..e3e72654850 100644 --- a/extensions/markdown/src/extension.ts +++ b/extensions/markdown/src/extension.ts @@ -12,7 +12,7 @@ import { MarkdownEngine } from './markdownEngine'; import DocumentLinkProvider from './documentLinkProvider'; import MDDocumentSymbolProvider from './documentSymbolProvider'; import { MDDocumentContentProvider, getMarkdownUri, isMarkdownFile } from './previewContentProvider'; -import { TableOfContentProvider } from './tableOfContentsProvider'; +import { TableOfContentsProvider } from './tableOfContentsProvider'; interface IPackageInfo { name: string; @@ -78,7 +78,7 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('_markdown.openDocumentLink', (args: OpenDocumentLinkArgs) => { const tryRevealLine = (editor: vscode.TextEditor) => { if (editor && args.fragment) { - const toc = new TableOfContentProvider(engine, editor.document); + const toc = new TableOfContentsProvider(engine, editor.document); const line = toc.lookup(args.fragment); if (!isNaN(line)) { return editor.revealRange( diff --git a/extensions/markdown/src/markdownEngine.ts b/extensions/markdown/src/markdownEngine.ts index 0a699fef56d..602aea8ea23 100644 --- a/extensions/markdown/src/markdownEngine.ts +++ b/extensions/markdown/src/markdownEngine.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import { TableOfContentsProvider } from './tableOfContentsProvider'; export interface IToken { type: string; @@ -45,13 +46,7 @@ export class MarkdownEngine { return `
    ${this.engine.utils.escapeHtml(str)}
    `; } }).use(mdnh, { - slugify: function (header: string) { - return encodeURI(header.trim() - .toLowerCase() - .replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '') - .replace(/\s+/g, '-') - .replace(/\-+$/, '')); - } + slugify: (header: string) => TableOfContentsProvider.slugify(header) }); for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'blockquote_open', 'list_item_open']) { diff --git a/extensions/markdown/src/tableOfContentsProvider.ts b/extensions/markdown/src/tableOfContentsProvider.ts index d2864ba66df..b3f484bb1e8 100644 --- a/extensions/markdown/src/tableOfContentsProvider.ts +++ b/extensions/markdown/src/tableOfContentsProvider.ts @@ -16,7 +16,7 @@ export interface TocEntry { location: vscode.Location; } -export class TableOfContentProvider { +export class TableOfContentsProvider { private toc: TocEntry[]; public constructor( @@ -35,7 +35,7 @@ export class TableOfContentProvider { } public lookup(fragment: string): number { - const slug = TableOfContentProvider.slugify(fragment); + const slug = TableOfContentsProvider.slugify(fragment); for (const entry of this.getToc()) { if (entry.slug === slug) { return entry.line; @@ -51,11 +51,11 @@ export class TableOfContentProvider { for (const heading of tokens.filter(token => token.type === 'heading_open')) { const lineNumber = heading.map[0]; const line = document.lineAt(lineNumber); - const href = TableOfContentProvider.slugify(line.text); + const href = TableOfContentsProvider.slugify(line.text); if (href) { toc.push({ slug: href, - text: TableOfContentProvider.getHeaderText(line.text), + text: TableOfContentsProvider.getHeaderText(line.text), line: lineNumber, location: new vscode.Location(document.uri, line.range) }); @@ -69,9 +69,11 @@ export class TableOfContentProvider { } public static slugify(header: string): string { - return encodeURI(TableOfContentProvider.getHeaderText(header) + return encodeURI(header.trim() .toLowerCase() - .replace(/\s/g, '-')); + .replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '') + .replace(/\s+/g, '-') + .replace(/\-+$/, '')); } } From 80a864ebc1fe31a04f3582ab6a201d0e56beee81 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 14 Feb 2017 17:12:37 -0800 Subject: [PATCH 052/140] Fix markdown editor link possibly breaking --- extensions/markdown/src/tableOfContentsProvider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/markdown/src/tableOfContentsProvider.ts b/extensions/markdown/src/tableOfContentsProvider.ts index b3f484bb1e8..831e1772b42 100644 --- a/extensions/markdown/src/tableOfContentsProvider.ts +++ b/extensions/markdown/src/tableOfContentsProvider.ts @@ -73,6 +73,7 @@ export class TableOfContentsProvider { .toLowerCase() .replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '') .replace(/\s+/g, '-') + .replace(/^\-+/, '') .replace(/\-+$/, '')); } } From f0907a01f8cb6fbaf707dccc1eb70e5ea3b714eb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 14 Feb 2017 15:37:08 -0800 Subject: [PATCH 053/140] Fix #19993 - peekLF and peekSingleByteChar apparently aren't inlined, hurting the perf in this very hot loop. The perf regression is partly from function calls, and partly from the function depending on a closure. It also seems like there might be more gains to be had if I refactor this file to not depend so heavily on closures and nested anonymous functions. --- .../search/node/worker/searchWorker.ts | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/services/search/node/worker/searchWorker.ts b/src/vs/workbench/services/search/node/worker/searchWorker.ts index c51657425f3..aed8cff2f91 100644 --- a/src/vs/workbench/services/search/node/worker/searchWorker.ts +++ b/src/vs/workbench/services/search/node/worker/searchWorker.ts @@ -170,12 +170,12 @@ export class SearchWorkerEngine { return resolve(null); } - let buffer = new Buffer(options.bufferLength); + const buffer = new Buffer(options.bufferLength); let pos: number; let i: number; let line = ''; let lineNumber = 0; - let lastBufferHadTraillingCR = false; + let lastBufferHadTrailingCR = false; const decodeBuffer = (buffer: NodeBuffer, start: number, end: number): string => { if (options.encoding === UTF8 || options.encoding === UTF8_with_bom) { @@ -203,6 +203,9 @@ export class SearchWorkerEngine { return clb(error); // return early if canceled or limit reached or no more bytes to read } + let crlfCharSize = 1; + let crBytes = [CR]; + let lfBytes = [LF]; pos = 0; i = 0; @@ -228,52 +231,49 @@ export class SearchWorkerEngine { options.encoding = UTF16le; break; } - } - // when we are running with UTF16le/be, LF and CR are encoded as - // two bytes, like 0A 00 (LF) / 0D 00 (CR) for LE or flipped around - // for BE. We need to account for this when splitting the buffer into - // newlines, and when detecting a CRLF combo. - let byteOffsetMultiplier = 1; - if (options.encoding === UTF16le || options.encoding === UTF16be) { - byteOffsetMultiplier = 2; - } - - const peekSingleByteChar = (char: number, offset = 0) => { - const from = i + offset; + // when we are running with UTF16le/be, LF and CR are encoded as + // two bytes, like 0A 00 (LF) / 0D 00 (CR) for LE or flipped around + // for BE. We need to account for this when splitting the buffer into + // newlines, and when detecting a CRLF combo. if (options.encoding === UTF16le) { - return buffer[from] === char && buffer[from + 1] === 0x00; + crlfCharSize = 2; + crBytes = [CR, 0x00]; + lfBytes = [LF, 0x00]; } else if (options.encoding === UTF16be) { - return buffer[from] === 0x00 && buffer[from + 1] === char; - } else { - return buffer[from] === char; + crlfCharSize = 2; + crBytes = [0x00, CR]; + lfBytes = [0x00, LF]; } - }; - const peekLF = (offset?: number) => peekSingleByteChar(LF, offset); - const peekCR = (offset?: number) => peekSingleByteChar(CR, offset); + } - if (lastBufferHadTraillingCR) { - if (peekLF()) { - lineFinished(1 * byteOffsetMultiplier); + if (lastBufferHadTrailingCR) { + if (buffer[i] === lfBytes[0] && (lfBytes.length === 1 || buffer[i + 1] === lfBytes[1])) { + lineFinished(1 * crlfCharSize); i++; } else { lineFinished(0); } - lastBufferHadTraillingCR = false; + lastBufferHadTrailingCR = false; } + /** + * This loop executes for every byte of every file in the workspace - it is highly performance-sensitive! + * Hence the duplication in reading the buffer to avoid a function call. Previously a function call was not + * being inlined by V8. + */ for (; i < bytesRead; ++i) { - if (peekLF()) { - lineFinished(1 * byteOffsetMultiplier); - } else if (peekCR()) { // CR (Carriage Return) - if (i + byteOffsetMultiplier === bytesRead) { - lastBufferHadTraillingCR = true; - } else if (peekLF(1 * byteOffsetMultiplier)) { - lineFinished(2 * byteOffsetMultiplier); - i += 2 * byteOffsetMultiplier - 1; + if (buffer[i] === lfBytes[0] && (lfBytes.length === 1 || buffer[i + 1] === lfBytes[1])) { + lineFinished(1 * crlfCharSize); + } else if (buffer[i] === crBytes[0] && (crBytes.length === 1 || buffer[i + 1] === crBytes[1])) { // CR (Carriage Return) + if (i + crlfCharSize === bytesRead) { + lastBufferHadTrailingCR = true; + } else if (buffer[i + crlfCharSize] === lfBytes[0] && (lfBytes.length === 1 || buffer[i + crlfCharSize + 1] === lfBytes[1])) { + lineFinished(2 * crlfCharSize); + i += 2 * crlfCharSize - 1; } else { - lineFinished(1 * byteOffsetMultiplier); + lineFinished(1 * crlfCharSize); } } } From 9860dc463059bfc2c6f078222c2d579d521b6234 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 14 Feb 2017 19:04:11 -0800 Subject: [PATCH 054/140] Only fire ITerminalInstance.onDisposed once per instance Fixes #19026 --- .../parts/terminal/electron-browser/terminalInstance.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index ab7cba19838..177bd077763 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -51,6 +51,7 @@ export class TerminalInstance implements ITerminalInstance { private _hadFocusOnExit: boolean; private _isLaunching: boolean; private _isVisible: boolean; + private _isDisposed: boolean; private _onDisposed: Emitter; private _onProcessIdReady: Emitter; private _onTitleChanged: Emitter; @@ -93,6 +94,7 @@ export class TerminalInstance implements ITerminalInstance { this._hadFocusOnExit = false; this._isLaunching = true; this._isVisible = false; + this._isDisposed = false; this._id = TerminalInstance._idCounter++; this._terminalHasTextContextKey = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.bindTo(this._contextKeyService); @@ -257,7 +259,10 @@ export class TerminalInstance implements ITerminalInstance { } this._process = null; } - this._onDisposed.fire(this); + if (!this._isDisposed) { + this._isDisposed = true; + this._onDisposed.fire(this); + } this._processDisposables = lifecycle.dispose(this._processDisposables); this._instanceDisposables = lifecycle.dispose(this._instanceDisposables); } From ff7254c6d6601e545ce07a3014586b059bbb7166 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 14 Feb 2017 19:26:39 -0800 Subject: [PATCH 055/140] Simplify terminal cursor blink styles --- .../terminal/electron-browser/media/xterm.css | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css index 56814595454..823868e0b13 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -99,42 +99,15 @@ } .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus .terminal-cursor, -.monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar):focus .terminal-cursor { animation: cursor-blink 1.2s infinite step-end; } -.vs-dark .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus .terminal-cursor, -.vs-dark .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar):focus .terminal-cursor { animation: cursor-blink-dark 1.2s infinite step-end; } -.hc-black .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus .terminal-cursor, -.hc-black .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar):focus .terminal-cursor { animation: cursor-blink-hc-black 1.2s infinite step-end; } +.monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar):focus .terminal-cursor { + animation: cursor-blink 1.2s infinite step-end; +} @keyframes cursor-blink { - 0% { - background-color: #333; - color: #CCC; - } + 0% {} 50% { background-color: transparent; - color: #333; - } -} - -@keyframes cursor-blink-dark { - 0% { - background-color: #CCC; - color: #1e1e1e; - } - 50% { - background-color: transparent; - color: #CCC; - } -} - -@keyframes cursor-blink-hc-black { - 0% { - background-color: #fff; - color: #000; - } - 50% { - background-color: transparent; - color: #FFF; + color: inherit; } } From f04798da0ee930b80ae58be6455286455bfd3964 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 14 Feb 2017 19:29:50 -0800 Subject: [PATCH 056/140] Expose terminal link matcher on ITerminalInstance Part of #18454 --- .../parts/terminal/common/terminal.ts | 18 ++++++++++++++++++ .../electron-browser/terminalInstance.ts | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index a1b2069f566..54cdb4900ed 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -179,6 +179,24 @@ export interface ITerminalInstance { */ dispose(): void; + /** + * Registers a link matcher, allowing custom link patterns to be matched and handled. + * @param regex The regular expression the search for, specifically this searches the + * textContent of the rows. You will want to use \s to match a space ' ' character for example. + * @param handler The callback when the link is called. + * @param matchIndex The index of the link from the regex.match(html) call. This defaults to 0 + * (for regular expressions without capture groups). + * @return The ID of the new matcher, this can be used to deregister. + */ + registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number): number; + + /** + * Deregisters a link matcher if it has been registered. + * @param matcherId The link matcher's ID (returned after register) + * @return Whether a link matcher was found and deregistered. + */ + deregisterLinkMatcher(matcherId: number): void; + /** * Check if anything is selected in terminal. */ diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 177bd077763..d462778e7e1 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -225,6 +225,14 @@ export class TerminalInstance implements ITerminalInstance { this.updateConfig(); } + public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number): number { + return this._xterm.registerLinkMatcher(regex, handler, matchIndex); + } + + public deregisterLinkMatcher(linkMatcherId: number): void { + this._xterm.deregisterLinkMatcher(linkMatcherId); + } + public hasSelection(): boolean { return !document.getSelection().isCollapsed; } From 175946ff277d40c0d1ed7ab90c19af7107991e37 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 15 Feb 2017 11:16:38 +0100 Subject: [PATCH 057/140] fixes #20612 --- scripts/test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 45eb9bb1fcc..9aa16fa3241 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,5 +1,4 @@ #!/bin/bash -set -e if [[ "$OSTYPE" == "darwin"* ]]; then realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } @@ -19,7 +18,7 @@ else fi INTENDED_VERSION="v`node -p "require('./package.json').electronVersion"`" -INSTALLED_VERSION=`cat .build/electron/version 2> /dev/null` +INSTALLED_VERSION=$(cat .build/electron/version 2> /dev/null) # Node modules test -d node_modules || ./scripts/npm.sh install From 7aa67af54d6a43340abf08ebf2879d56594490eb Mon Sep 17 00:00:00 2001 From: Michel Kaporin Date: Wed, 15 Feb 2017 11:20:53 +0100 Subject: [PATCH 058/140] Reverted HC theme back to original solution with red outline for column selection. --- .../parts/debug/browser/media/debug.contribution.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css index 1a062e81750..5b9d119b8be 100644 --- a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css @@ -242,11 +242,11 @@ } .hc-black .monaco-editor .debug-top-stack-frame-line { - background: rgba(255, 255, 0, .87); + background: rgba(255, 246, 0, .87); mix-blend-mode: lighten; /* Preserves text selection's contrast */ } .hc-black .monaco-editor .debug-top-stack-frame-column { background: none; - outline: 2px solid #f38518; + outline: 2px solid #ff0000; } From f57d5dda5c689d4b4c236171853d31e4927c5bcf Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Feb 2017 11:29:27 +0100 Subject: [PATCH 059/140] Fix #20641 --- .../configuration/common/configurationRegistry.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 08cdd95782f..b5181490056 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -122,13 +122,13 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateOverridePropertyPatternKey(); } - public registerDefaultConfigurations(defaultConnfigurations: IDefaultConfigurationExtension[]): void { + public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void { const configurationNode: IConfigurationNode = { id: 'defaultOverrides', title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"), properties: {} }; - for (const defaultConfiguration of defaultConnfigurations) { + for (const defaultConfiguration of defaultConfigurations) { for (const key in defaultConfiguration.defaults) { const defaultValue = defaultConfiguration.defaults[key]; if (OVERRIDE_PROPERTY_PATTERN.test(key) && typeof defaultValue === 'object') { @@ -141,7 +141,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } } - this.registerConfiguration(configurationNode, false); + if (Object.keys(configurationNode.properties).length) { + this.registerConfiguration(configurationNode, false); + } } private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, overridable: boolean = false) { @@ -360,10 +362,8 @@ configurationExtPoint.setHandler(extensions => { validateProperties(configuration, collector); - if (configuration.properties) { - configuration.id = extensions[i].description.id; - configurations.push(configuration); - } + configuration.id = extensions[i].description.id; + configurations.push(configuration); } configurationRegistry.registerConfigurations(configurations, false); From 5fea24a9ad7d86ac4068005f63303042b430d566 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 15 Feb 2017 11:35:21 +0100 Subject: [PATCH 060/140] Add right margin in the Exception badge in the call stack fixes #20603 --- src/vs/workbench/parts/debug/browser/media/debugViewlet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/browser/media/debugViewlet.css b/src/vs/workbench/parts/debug/browser/media/debugViewlet.css index cff9d97eb53..a1b08895981 100644 --- a/src/vs/workbench/parts/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/parts/debug/browser/media/debugViewlet.css @@ -173,7 +173,7 @@ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - padding-left: 5px; + margin: 0px 10px; } .debug-viewlet .debug-call-stack-title > .pause-message > .label { From d871d315c8f7438ba5b9d13098d0e27cd3af276e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 15 Feb 2017 11:58:33 +0100 Subject: [PATCH 061/140] fixes #20344 --- .vscode/launch.json | 13 ++++++++++--- .vscode/tasks.json | 9 ++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a08a3231806..dc44946fc21 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,11 +7,17 @@ "name": "Unit Tests", "protocol": "legacy", "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeExecutable": "${execPath}", + "runtimeExecutable": "${workspaceRoot}/.build/electron/Code - OSS.app/Contents/MacOS/Electron", + "windows": { + "runtimeExecutable": "" + }, + "linux": { + "runtimeExecutable": "" + }, "stopOnEntry": false, "args": [ "--timeout", - "999999" + "2000" ], "cwd": "${workspaceRoot}", "env": { @@ -20,7 +26,8 @@ "sourceMaps": true, "outFiles": [ "${workspaceRoot}/out/**/*.js" - ] + ], + "preLaunchTask": "electron" }, { "type": "node", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 88e68fde6d5..a52055b9636 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -47,7 +47,7 @@ ], "severity": "warning", "pattern": { - "regexp": "(.*)\\[(\\d+),\\s(\\d+)\\]:\\s(.*)$", // (.*)\[(\d+), (\d+)\]: (.*) + "regexp": "(.*)\\[(\\d+),\\s(\\d+)\\]:\\s(.*)$", // (.*)\[(\d+), (\d+)\]: (.*) "file": 1, "line": 2, "column": 3, @@ -62,6 +62,13 @@ ], "showOutput": "always", "isTestCommand": true + }, + { + "taskName": "electron", + "args": [ + "--no-color" + ], + "showOutput": "never" } ] } \ No newline at end of file From 08be8f251fe70818c9dca1a7c77ae8b63195e54c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 15 Feb 2017 12:04:04 +0100 Subject: [PATCH 062/140] show a message when no definition/implementation etc could be found --- .../browser/goToDeclaration.css | 21 +++++ .../browser/goToDeclaration.ts | 87 +++++++++++++++++-- 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css index 058ef445e0f..2493cde3d82 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css @@ -3,6 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-editor .monaco-editor-overlaymessage { + padding-bottom: 8px; +} + +.monaco-editor .monaco-editor-overlaymessage .message { + padding: 1px 4px; + background: #1382CE; + color: white; +} + +.monaco-editor .monaco-editor-overlaymessage .anchor { + width: 0 !important; + height: 0 !important; + border-color: transparent; + border-style: solid; + z-index: 1000; + border-width: 8px; + border-top-color: #1382CE; + position: absolute; +} + .monaco-editor .preview-zone-widget.preview .monaco-editor, .monaco-editor .preview-zone-widget.preview .monaco-editor-background, .monaco-editor .preview-zone-widget.preview .monaco-editor .line-numbers, diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index 18e0271ade8..9917cfaed51 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -8,6 +8,7 @@ import 'vs/css!./goToDeclaration'; import * as nls from 'vs/nls'; import { Throttler } from 'vs/base/common/async'; +import { any, filterEvent } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MarkedString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; @@ -24,7 +25,7 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { editorAction, IActionOptions, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; import { Location, DefinitionProviderRegistry } from 'vs/editor/common/modes'; -import { ICodeEditor, IEditorMouseEvent, IMouseTarget } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, IMouseTarget, IContentWidget, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition } from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; import { ReferencesController } from 'vs/editor/contrib/referenceSearch/browser/referencesController'; @@ -34,16 +35,87 @@ import { PeekContext } from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; import * as corePosition from 'vs/editor/common/core/position'; - import ModeContextKeys = editorCommon.ModeContextKeys; import EditorContextKeys = editorCommon.EditorContextKeys; +class MessageOverlay implements IContentWidget { + + private static _last: IDisposable; + + static show(editor: ICodeEditor, pos: editorCommon.IPosition, message: string): IDisposable { + + dispose(MessageOverlay._last); + + const widget = new MessageOverlay(message, pos.lineNumber, editor.getOffsetForColumn(pos.lineNumber, pos.column)); + const remove = () => editor.removeContentWidget(widget); + editor.addContentWidget(widget); + + + const unhook = any( + filterEvent(editor.onMouseMove, e => { + const { position } = e.target; + return Math.abs(position.lineNumber - pos.lineNumber) > 1 + || Math.abs(position.column - pos.column) > 10; + }), + editor.onDidChangeCursorPosition, + editor.onDidBlurEditorText, + editor.onDidDispose, + editor.onDidChangeModel + )(_ => { + unhook.dispose(); + remove(); + }); + + MessageOverlay._last = unhook; + + return unhook; + } + + readonly allowEditorOverflow = true; + readonly suppressMouseDown = false; + + private _position: editorCommon.IPosition; + private _domNode: HTMLDivElement; + + constructor(text: string, lineNumber: number, horizontalOffset: number) { + this._position = { lineNumber, column: 1 }; + + this._domNode = document.createElement('div'); + this._domNode.style.paddingLeft = `${horizontalOffset - 6}px`; + this._domNode.classList.add('monaco-editor-overlaymessage'); + + + const message = document.createElement('div'); + message.classList.add('message'); + message.textContent = text; + this._domNode.appendChild(message); + + const anchor = document.createElement('div'); + anchor.classList.add('anchor'); + this._domNode.appendChild(anchor); + } + + getId(): string { + return 'messageoverlay'; + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + getPosition(): IContentWidgetPosition { + return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE] }; + } +} + + export class DefinitionActionConfig { constructor( public openToSide = false, public openInPeek = false, - public filterCurrent = true + public filterCurrent = true, + public noResultsMessage = nls.localize('generic.noResults', "Could not find anything") ) { // } @@ -67,10 +139,6 @@ export class DefinitionAction extends EditorAction { return this.getDeclarationsAtPosition(model, pos).then(references => { - if (!references) { - return; - } - // * remove falsy references // * remove reference at the current pos // * collapse ranges to start pos @@ -80,7 +148,7 @@ export class DefinitionAction extends EditorAction { if (!reference || !reference.range) { continue; } - let {uri, range} = reference; + let { uri, range } = reference; if (!this._configuration.filterCurrent || uri.toString() !== model.uri.toString() || !Range.containsPosition(range, pos)) { @@ -93,6 +161,7 @@ export class DefinitionAction extends EditorAction { } if (result.length === 0) { + MessageOverlay.show(editor, pos, this._configuration.noResultsMessage); return; } @@ -125,7 +194,7 @@ export class DefinitionAction extends EditorAction { } private _openReference(editorService: IEditorService, reference: Location, sideBySide: boolean): TPromise { - let {uri, range} = reference; + let { uri, range } = reference; return editorService.openEditor({ resource: uri, options: { From 627562f125eb0ba605fcc2eb0ccf8fd550f0c8fc Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 15 Feb 2017 12:51:54 +0100 Subject: [PATCH 063/140] debug: breakpoint decoration has no range, only use the margin decoration fixes #20601 --- src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts index c2e8160de35..a033b7c66fa 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts @@ -239,7 +239,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { private createBreakpointDecorations(breakpoints: IBreakpoint[]): IModelDeltaDecoration[] { return breakpoints.map((breakpoint) => { const range = breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1) - : new Range(breakpoint.lineNumber, 1, breakpoint.lineNumber, Number.MAX_VALUE); + : new Range(breakpoint.lineNumber, 1, breakpoint.lineNumber, 1); return { options: this.getBreakpointDecorationOptions(breakpoint), range From e516d601719a6ecfeb9a4032c32946ad069931e3 Mon Sep 17 00:00:00 2001 From: Michel Kaporin Date: Wed, 15 Feb 2017 12:58:30 +0100 Subject: [PATCH 064/140] Added lineNumber to common pre-defined variables for tasks.json/launch.json. --- .../node/configurationResolverService.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts index 1a7cb08cece..5c51348b856 100644 --- a/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts @@ -13,6 +13,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { toResource } from 'vs/workbench/common/editor'; @@ -77,6 +78,19 @@ export class ConfigurationResolverService implements IConfigurationResolverServi return paths.extname(this.getFilePath()); } + private get lineNumber(): string { + const activeEditor = this.editorService.getActiveEditor(); + if (activeEditor) { + const editorControl = (activeEditor.getControl()); + if (editorControl) { + const lineNumber = editorControl.getSelection().positionLineNumber; + return String(lineNumber); + } + } + + return ''; + } + private getFilePath(): string { let input = this.editorService.getActiveEditorInput(); if (!input) { From 7ecce78edd5677beefffa1b033043987ebe52448 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 15 Feb 2017 14:45:47 +0100 Subject: [PATCH 065/140] update node-debug --- build/gulpfile.vscode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index d224fb80a04..9c3572d1834 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.10.9' }, + { name: 'ms-vscode.node-debug', version: '1.10.10' }, { name: 'ms-vscode.node-debug2', version: '1.9.13' } ]; From 9c49b8510c1120ec490f9f326f2d24866529df13 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 15 Feb 2017 15:26:15 +0100 Subject: [PATCH 066/140] update node-debug (returning correct length in completionProposalRequest) --- build/gulpfile.vscode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 9c3572d1834..85d0699f97a 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.10.10' }, + { name: 'ms-vscode.node-debug', version: '1.10.11' }, { name: 'ms-vscode.node-debug2', version: '1.9.13' } ]; From fce3d86e3433b8ecde0f8c7923856c04368d3a3c Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 15 Feb 2017 15:45:26 +0100 Subject: [PATCH 067/140] update debug protocol --- .../parts/debug/common/debugProtocol.d.ts | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts index 296421a722b..92471938bb7 100644 --- a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts @@ -224,7 +224,9 @@ declare module DebugProtocol { /** Arguments for 'initialize' request. */ export interface InitializeRequestArguments { - /** The ID of the debugger adapter. Used to select or verify debugger adapter. */ + /** The ID of the (frontend) client using this adapter. */ + clientID?: string; + /** The ID of the debug adapter. */ adapterID: string; /** If true all line numbers are 1-based (default). */ linesStartAt1?: boolean; @@ -696,6 +698,8 @@ declare module DebugProtocol { name: string; /** The value of the variable. */ value: string; + /** Specifies details on how to format the response value. */ + format?: ValueFormat; } /** Response to 'setVariable' request. */ @@ -903,6 +907,34 @@ declare module DebugProtocol { }; } + /** ExceptionInfoRequest request; value of command field is 'exceptionInfo'. + Retrieves the details of the exception that caused the StoppedEvent to be raised. + */ + export interface ExceptionInfoRequest extends Request { + // command: 'exceptionInfo'; + arguments: ExceptionInfoArguments; + } + + /** Arguments for 'exceptionInfo' request. */ + export interface ExceptionInfoArguments { + /** Thread for which exception information should be retrieved. */ + threadId: number; + } + + /** Response to 'exceptionInfo' request. */ + export interface ExceptionInfoResponse extends Response { + body: { + /** ID of the exception that was thrown. */ + exceptionId: string; + /** Descriptive text for the exception provided by the debug adapter. */ + description?: string; + /** Mode that caused the exception notification to be raised. */ + breakMode: ExceptionBreakMode; + /** Detailed information about the exception. */ + details?: ExceptionDetails; + }; + } + /** Information about the capabilities of a debug adapter. */ export interface Capabilities { /** The debug adapter supports the configurationDoneRequest. */ @@ -941,6 +973,8 @@ declare module DebugProtocol { supportsExceptionOptions?: boolean; /** The debug adapter supports a 'format' attribute on the stackTraceRequest, variablesRequest, and evaluateRequest. */ supportsValueFormattingOptions?: boolean; + /** The debug adapter supports the exceptionInfo request. */ + supportsExceptionInfoRequest?: boolean; } /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ @@ -1212,10 +1246,13 @@ declare module DebugProtocol { text?: string; /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ type?: CompletionItemType; - /** When a completion is selected it replaces 'length' characters starting at 'start' in the text passed to the CompletionsRequest. - If missing the frontend will try to determine these values heuristically. + /** This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added. + If missing the text is added at the location specified by the CompletionsRequest's 'column' attribute. */ start?: number; + /** This value determines how many characters are overwritten by the completion text. + If missing the value 0 is assumed which results in the completion text being inserted. + */ length?: number; } @@ -1278,5 +1315,21 @@ declare module DebugProtocol { /** Depending on the value of 'negate' the names that should match or not match. */ names: string[]; } + + /** Detailed information about an exception that has occurred. */ + export interface ExceptionDetails { + /** Message contained in the exception. */ + message?: string; + /** Short type name of the exception object. */ + typeName?: string; + /** Fully-qualified type name of the exception object. */ + fullTypeName?: string; + /** Optional expression that can be evaluated in the current scope to obtain the exception object. */ + evaluateName?: string; + /** Stack trace at the time the exception was thrown. */ + stackTrace?: string; + /** Details of the exception contained by this exception, if any. */ + innerException?: ExceptionDetails[]; + } } From a731d395bdea58a4a7e75707e66e182962ea3190 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 15 Feb 2017 15:56:41 +0100 Subject: [PATCH 068/140] debug: do not open deemphesized sources in editor fixes #20622 --- src/vs/workbench/parts/debug/common/debugModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 1ed306634bc..c9944c7c90b 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -367,7 +367,7 @@ export class StackFrame implements debug.IStackFrame { } public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise { - return editorService.openEditor({ + return this.source.deemphasize ? TPromise.as(true) : editorService.openEditor({ resource: this.source.uri, description: this.source.origin, options: { From 98591f119cd85a459cc914e97b44b2c8d6af9b07 Mon Sep 17 00:00:00 2001 From: Michel Kaporin Date: Wed, 15 Feb 2017 15:59:05 +0100 Subject: [PATCH 069/140] Updated test suite with test for the lineNumber config variable. --- .../test/node/configurationResolverService.test.ts | 4 ++++ src/vs/workbench/test/workbenchTestServices.ts | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts index 36afabc1971..1b8b94b15da 100644 --- a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts @@ -41,6 +41,10 @@ suite('Configuration Resolver Service', () => { assert.strictEqual(configurationResolverService.resolve('abc ${workspaceRootFolderName} xyz'), 'abc workspaceLocation xyz'); }); + test('current selected line number', () => { + assert.strictEqual(configurationResolverService.resolve('abc ${lineNumber} xyz'), 'abc 15 xyz'); + }); + test('substitute many', () => { if (platform.isWindows) { assert.strictEqual(configurationResolverService.resolve('${workspaceRoot} - ${workspaceRoot}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation'); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 28e07e64337..a574451b0c0 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -498,7 +498,19 @@ export class TestEditorService implements IWorkbenchEditorService { public getActiveEditor(): IEditor { this.callback('getActiveEditor'); - return null; + return { + input: null, + options: null, + position: null, + getId: () => { return null; }, + getControl: () => { + return { + getSelection: () => { return { positionLineNumber: 15 }; } + }; + }, + focus: () => { }, + isVisible: () => { return true; } + }; } public getActiveEditorInput(): IEditorInput { From e63560b25478f11bd6c04d5adccc8a16e42cc41c Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 15 Feb 2017 14:57:33 +0100 Subject: [PATCH 070/140] list: refactor --- src/vs/base/browser/ui/list/listWidget.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index e329c16252c..c0bde005eda 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -89,8 +89,7 @@ class Trait implements ISpliceable, IDisposable { ...this.indexes.filter(i => i >= end).map(i => i + diff) ]; - this.indexes = indexes; - this._onChange.fire({ indexes }); + this.set(indexes); } renderElement(element: T, index: number, container: HTMLElement): void { From 3ab33cac7599f32c5888747ef13d5879b129b120 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 15 Feb 2017 16:06:03 +0100 Subject: [PATCH 071/140] fix list open --- src/vs/base/browser/ui/list/list.ts | 15 ++++------ src/vs/base/browser/ui/list/listPaging.ts | 6 ++-- src/vs/base/browser/ui/list/listWidget.ts | 28 ++++++++++++------- .../contrib/suggest/browser/suggestWidget.ts | 8 +++--- src/vs/workbench/electron-browser/commands.ts | 3 +- .../parts/scm/electron-browser/scmViewlet.ts | 2 +- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 8b9b4088705..53df874292d 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -21,17 +21,12 @@ export interface IListElementEvent { event: E; } +export interface IListEvent { + elements: T[]; + indexes: number[]; +} + export interface IListMouseEvent extends MouseEvent { element: T; index: number; -} - -export interface IFocusChangeEvent { - elements: T[]; - indexes: number[]; -} - -export interface ISelectionChangeEvent { - elements: T[]; - indexes: number[]; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 33347b680ca..70170ab730b 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -6,7 +6,7 @@ import 'vs/css!./list'; import { IDisposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; -import { IDelegate, IRenderer, IFocusChangeEvent, ISelectionChangeEvent } from './list'; +import { IDelegate, IRenderer, IListEvent } from './list'; import { List, IListOptions } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import Event, { mapEvent } from 'vs/base/common/event'; @@ -77,11 +77,11 @@ export class PagedList { return this.list; } - get onFocusChange(): Event> { + get onFocusChange(): Event> { return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } - get onSelectionChange(): Event> { + get onSelectionChange(): Event> { return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index c0bde005eda..8cbae824f80 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -13,7 +13,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import Event, { Emitter, EventBufferer, chain, mapEvent, fromCallback } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IDelegate, IRenderer, IListMouseEvent, IFocusChangeEvent, ISelectionChangeEvent } from './list'; +import { IDelegate, IRenderer, IListEvent, IListMouseEvent } from './list'; import { ListView, IListViewOptions } from './listView'; export interface IIdentityProvider { @@ -183,7 +183,7 @@ class KeyboardController implements IDisposable { private onEnter(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.setSelection(this.list.getFocus()); + this.list.open(this.list.getFocus()); } private onUpArrow(e: StandardKeyboardEvent): void { @@ -224,6 +224,7 @@ class KeyboardController implements IDisposable { } class MouseController implements IDisposable { + private disposables: IDisposable[]; constructor( @@ -246,7 +247,7 @@ class MouseController implements IDisposable { e.stopPropagation(); this.view.domNode.focus(); this.list.setFocus([e.index]); - this.list.setSelection([e.index]); + this.list.open([e.index]); } dispose() { @@ -278,21 +279,23 @@ export class List implements ISpliceable, IDisposable { private spliceable: ISpliceable; private disposables: IDisposable[]; - @memoize - get onFocusChange(): Event> { + @memoize get onFocusChange(): Event> { return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); } - @memoize - get onSelectionChange(): Event> { + @memoize get onSelectionChange(): Event> { return mapEvent(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e)); } - @memoize - get onContextMenu(): Event> { + @memoize get onContextMenu(): Event> { return fromCallback(handler => this.view.addListener('contextmenu', handler)); } + private _onOpen = new Emitter(); + @memoize get onOpen(): Event> { + return mapEvent(this._onOpen.event, indexes => this.toListEvent({ indexes })); + } + private _onDOMFocus = new Emitter(); get onDOMFocus(): Event { return this._onDOMFocus.event; } @@ -500,7 +503,7 @@ export class List implements ISpliceable, IDisposable { } } - getElementDomId(index: number): string { + private getElementDomId(index: number): string { return `${this.idPrefix}_${index}`; } @@ -512,6 +515,11 @@ export class List implements ISpliceable, IDisposable { return this.view.domNode; } + open(indexes: number[]): void { + this.setSelection(indexes); + this._onOpen.fire(indexes); + } + private toListEvent({ indexes }: ITraitChangeEvent) { return { indexes, elements: indexes.map(i => this.view.element(i)) }; } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 4e1bee69956..aada91728c4 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -14,7 +14,7 @@ import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { addClass, append, $, hide, removeClass, show, toggleClass } from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { IDelegate, IFocusChangeEvent, IRenderer, ISelectionChangeEvent } from 'vs/base/browser/ui/list/list'; +import { IDelegate, IListEvent, IRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -407,7 +407,7 @@ export class SuggestWidget implements IContentWidget, IDelegate }); } - private onListSelection(e: ISelectionChangeEvent): void { + private onListSelection(e: IListEvent): void { if (!e.elements.length) { return; } @@ -439,7 +439,7 @@ export class SuggestWidget implements IContentWidget, IDelegate } } - private onListFocus(e: IFocusChangeEvent): void { + private onListFocus(e: IListEvent): void { if (!e.elements.length) { if (this.currentSuggestionDetails) { this.currentSuggestionDetails.cancel(); @@ -591,7 +591,7 @@ export class SuggestWidget implements IContentWidget, IDelegate this.completionModel = null; } else { - const {stats} = this.completionModel; + const { stats } = this.completionModel; stats['wasAutomaticallyTriggered'] = !!isAuto; this.telemetryService.publicLog('suggestWidget', stats); diff --git a/src/vs/workbench/electron-browser/commands.ts b/src/vs/workbench/electron-browser/commands.ts index 69b92e88a7b..3b075c567fb 100644 --- a/src/vs/workbench/electron-browser/commands.ts +++ b/src/vs/workbench/electron-browser/commands.ts @@ -266,8 +266,7 @@ export function registerCommands(): void { // List if (focused instanceof List) { const list = focused; - - list.setSelection(list.getFocus()); + list.open(list.getFocus()); } // Tree diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index cf2e6d457af..96ec709587b 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -222,7 +222,7 @@ export class SCMViewlet extends Viewlet { this.disposables.push(this.listService.register(this.list)); - chain(this.list.onSelectionChange) + chain(this.list.onOpen) .map(e => e.elements[0]) .filter(e => !!e && isSCMResource(e)) .on(this.open, this, this.disposables); From 5005924ee836ac1efdc76ad9fbb23fc3e4e32a5d Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 15 Feb 2017 16:15:33 +0100 Subject: [PATCH 072/140] debug: color polish --- .../parts/debug/browser/media/debug.contribution.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css index 5b9d119b8be..fe64eac3c4e 100644 --- a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css @@ -9,11 +9,11 @@ } .monaco-editor .debug-top-stack-frame-line { - background: rgba(255, 255, 102, 0.25); + background: rgba(255, 255, 102, 0.3); } .monaco-editor .debug-top-stack-frame-exception-line { - background: rgba(242, 222, 222, 0.7); + background: rgba(242, 222, 222, 0.8); } .monaco-editor .debug-top-stack-frame-column { @@ -242,11 +242,11 @@ } .hc-black .monaco-editor .debug-top-stack-frame-line { - background: rgba(255, 246, 0, .87); + background: rgba(255, 246, 0, .8); mix-blend-mode: lighten; /* Preserves text selection's contrast */ } .hc-black .monaco-editor .debug-top-stack-frame-column { background: none; - outline: 2px solid #ff0000; + outline: 2px solid #f38518; } From 69fdfa2fd83f8089dcd7b2577a117efc765677ab Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 15 Feb 2017 15:36:37 +0100 Subject: [PATCH 073/140] debt - move IMenu closing to its service --- src/vs/platform/actions/common/actions.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index ed8cc1ebd72..2759241230f 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -21,11 +21,6 @@ export interface ICommandAction { iconClass?: string; } -export interface IMenu extends IDisposable { - onDidChange: Event; - getActions(arg?: any): [string, MenuItemAction[]][]; -} - export interface IMenuItem { command: ICommandAction; alt?: ICommandAction; @@ -60,6 +55,11 @@ export class MenuId { } } +export interface IMenu extends IDisposable { + onDidChange: Event; + getActions(arg?: any): [string, MenuItemAction[]][]; +} + export const IMenuService = createDecorator('menuService'); export interface IMenuService { @@ -95,7 +95,7 @@ export const MenuRegistry: IMenuRegistry = new class { return this.commands[id]; } - appendMenuItem({id}: MenuId, item: IMenuItem): IDisposable { + appendMenuItem({ id }: MenuId, item: IMenuItem): IDisposable { let array = this.menuItems[id]; if (!array) { this.menuItems[id] = array = [item]; @@ -112,7 +112,7 @@ export const MenuRegistry: IMenuRegistry = new class { }; } - getMenuItems({id}: MenuId): IMenuItem[] { + getMenuItems({ id }: MenuId): IMenuItem[] { return this.menuItems[id] || []; } }; @@ -204,4 +204,4 @@ export class SyncActionDescriptor { public get keybindingWeight(): number { return this._keybindingWeight; } -} \ No newline at end of file +} From 7eb690f2b0337407629ae5c9220eda39a343bfc6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 15 Feb 2017 15:38:51 +0100 Subject: [PATCH 074/140] debt - remove `getCommandActions` as there is now an explicit command palette menu --- src/vs/editor/browser/standalone/simpleServices.ts | 7 +------ src/vs/platform/actions/common/actions.ts | 2 -- src/vs/platform/actions/common/menuService.ts | 7 +------ 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index 894a39915b5..5a570fa542e 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -28,8 +28,7 @@ import { IDisposable, IReference, ImmortalReference, combinedDisposable } from ' import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { values } from 'vs/base/common/collections'; -import { MenuId, MenuRegistry, ICommandAction, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { Menu } from 'vs/platform/actions/common/menu'; import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; @@ -428,10 +427,6 @@ export class SimpleMenuService implements IMenuService { public createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu { return new Menu(id, TPromise.as(true), this._commandService, contextKeyService); } - - public getCommandActions(): ICommandAction[] { - return values(MenuRegistry.commands); - } } export class StandaloneTelemetryService implements ITelemetryService { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 2759241230f..824888c2906 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -67,8 +67,6 @@ export interface IMenuService { _serviceBrand: any; createMenu(id: MenuId, scopedKeybindingService: IContextKeyService): IMenu; - - getCommandActions(): ICommandAction[]; } export interface IMenuRegistry { diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 87d79822189..77ee1f52905 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -5,9 +5,8 @@ 'use strict'; -import { values } from 'vs/base/common/collections'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuId, MenuRegistry, ICommandAction, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { Menu } from 'vs/platform/actions/common/menu'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -26,8 +25,4 @@ export class MenuService implements IMenuService { createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu { return new Menu(id, this._extensionService.onReady(), this._commandService, contextKeyService); } - - getCommandActions(): ICommandAction[] { - return values(MenuRegistry.commands); - } } From 542e4bf0bc861150c47ae17b3a2f293697e1aadd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 15 Feb 2017 15:57:09 +0100 Subject: [PATCH 075/140] menu - move command palette special into registry --- src/vs/platform/actions/common/actions.ts | 24 ++++++++++++++++++- src/vs/platform/actions/common/menu.ts | 14 +---------- .../actions/test/common/menuService.test.ts | 16 +++++++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 824888c2906..23da584e1a1 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -111,7 +111,29 @@ export const MenuRegistry: IMenuRegistry = new class { } getMenuItems({ id }: MenuId): IMenuItem[] { - return this.menuItems[id] || []; + const result = this.menuItems[id] || []; + + if (id === MenuId.CommandPalette.id) { + // CommandPalette is special because it shows + // all commands by default + this._appendImplicitItems(result); + } + return result; + } + + private _appendImplicitItems(result: IMenuItem[]) { + const set = new Set(); + for (const { command, alt } of result) { + set.add(command.id); + if (alt) { + set.add(alt.id); + } + } + for (let id in this.commands) { + if (!set.has(id)) { + result.push({ command: this.commands[id] }); + } + } } }; diff --git a/src/vs/platform/actions/common/menu.ts b/src/vs/platform/actions/common/menu.ts index 3c54f3b75e3..3f0c14e8bc8 100644 --- a/src/vs/platform/actions/common/menu.ts +++ b/src/vs/platform/actions/common/menu.ts @@ -12,8 +12,6 @@ import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { values } from 'vs/base/common/collections'; -import { index } from 'vs/base/common/arrays'; type MenuItemGroup = [string, IMenuItem[]]; @@ -30,17 +28,7 @@ export class Menu implements IMenu { @IContextKeyService private _contextKeyService: IContextKeyService ) { startupSignal.then(_ => { - let menuItems = MenuRegistry.getMenuItems(id); - - if (id === MenuId.CommandPalette) { - const ids = index(menuItems, i => i.command.id); - const commandMenuItems = values(MenuRegistry.commands) - .filter(c => !ids[c.id]) - .map(command => ({ command })); - - menuItems = [...menuItems, ...commandMenuItems]; - } - + const menuItems = MenuRegistry.getMenuItems(id); const keysFilter = new Set(); let group: MenuItemGroup; diff --git a/src/vs/platform/actions/test/common/menuService.test.ts b/src/vs/platform/actions/test/common/menuService.test.ts index 4cc8cc02d73..b8408b178ee 100644 --- a/src/vs/platform/actions/test/common/menuService.test.ts +++ b/src/vs/platform/actions/test/common/menuService.test.ts @@ -186,4 +186,20 @@ suite('MenuService', function () { assert.equal(two.id, 'b'); assert.equal(three.id, 'a'); }); + + test('special MenuId palette', function () { + + disposables.push(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { id: 'a', title: 'Explicit' } + })); + + MenuRegistry.addCommand({ id: 'b', title: 'Implicit' }); + + const [first, second] = MenuRegistry.getMenuItems(MenuId.CommandPalette); + assert.equal(first.command.id, 'a'); + assert.equal(first.command.title, 'Explicit'); + + assert.equal(second.command.id, 'b'); + assert.equal(second.command.title, 'Implicit'); + }); }); From a98afa69a823a79c14bc8e2efd93cc42b6acaec9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 15 Feb 2017 16:29:40 +0100 Subject: [PATCH 076/140] menu - move custom label logic to where it is used --- src/vs/platform/actions/common/actions.ts | 5 ++-- src/vs/platform/actions/common/menu.ts | 9 +------ .../quickopen/browser/commandsHandler.ts | 24 +++++++++++-------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 23da584e1a1..590fd423b9e 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -161,18 +161,17 @@ export class MenuItemAction extends ExecuteCommandAction { constructor( item: ICommandAction, - label: string, alt: ICommandAction, arg: any, @ICommandService commandService: ICommandService ) { - super(item.id, label, commandService); + super(item.id, item.title, commandService); this._cssClass = item.iconClass; this._enabled = true; this._arg = arg; this.item = item; - this.alt = alt ? new MenuItemAction(alt, alt.title, undefined, arg, commandService) : undefined; + this.alt = alt ? new MenuItemAction(alt, undefined, arg, commandService) : undefined; } run(): TPromise { diff --git a/src/vs/platform/actions/common/menu.ts b/src/vs/platform/actions/common/menu.ts index 3f0c14e8bc8..e4b241d8bb8 100644 --- a/src/vs/platform/actions/common/menu.ts +++ b/src/vs/platform/actions/common/menu.ts @@ -8,7 +8,6 @@ import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; -import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -77,13 +76,7 @@ export class Menu implements IMenu { const activeActions: MenuItemAction[] = []; for (const item of items) { if (this._contextKeyService.contextMatchesRules(item.when)) { - let title = item.command.title; - - if (this.id === MenuId.CommandPalette && item.command.category) { - title = localize('', "{0}: {1}", item.command.category, title); - } - - const action = new MenuItemAction(item.command, title, item.alt, arg, this._commandService); + const action = new MenuItemAction(item.command, item.alt, arg, this._commandService); action.order = item.order; //TODO@Ben order is menu item property, not an action property activeActions.push(action); } diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts index ad7f6340bae..19393709f9a 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts @@ -16,7 +16,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import strings = require('vs/base/common/strings'); import { Mode, IEntryRunContext, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntryGroup, IHighlight, QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { SyncActionDescriptor, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; import { Registry } from 'vs/platform/platform'; @@ -277,8 +277,8 @@ export class CommandsHandler extends QuickOpenHandler { // Other Actions const menu = this.menuService.createMenu(MenuId.CommandPalette, this.contextKeyService); - const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []); - const commandEntries = this.commandActionsToEntries(menuActions, searchValue); + const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []); + const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue); // Concat let entries = [...workbenchEntries, ...editorEntries, ...commandEntries]; @@ -355,17 +355,21 @@ export class CommandsHandler extends QuickOpenHandler { return entries; } - private commandActionsToEntries(actions: IAction[], searchValue: string): ActionCommandEntry[] { + private menuItemActionsToEntries(actions: MenuItemAction[], searchValue: string): ActionCommandEntry[] { const entries: ActionCommandEntry[] = []; for (let action of actions) { - const [keybind] = this.keybindingService.lookupKeybindings(action.id); + const label = action.item.category + ? nls.localize('cat.title', "{0}: {1}", action.item.category, action.item.title) + : action.item.title; + const highlights = wordFilter(searchValue, label); + if (!highlights) { + continue; + } + const [keybind] = this.keybindingService.lookupKeybindings(action.item.id); const keyLabel = keybind ? this.keybindingService.getLabelFor(keybind) : ''; const keyAriaLabel = keybind ? this.keybindingService.getAriaLabelFor(keybind) : ''; - const highlights = wordFilter(searchValue, action.label); - if (highlights) { - entries.push(this.instantiationService.createInstance(ActionCommandEntry, keyLabel, keyAriaLabel, action.label, null, highlights, null, action)); - } + entries.push(this.instantiationService.createInstance(ActionCommandEntry, keyLabel, keyAriaLabel, label, null, highlights, null, action)); } return entries; @@ -398,4 +402,4 @@ export class EditorCommandsHandler extends CommandsHandler { protected includeWorkbenchCommands(): boolean { return false; } -} \ No newline at end of file +} From b7dc5cd88bd76d368db5b7c9972c088f533e2bfd Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 15 Feb 2017 16:31:06 +0100 Subject: [PATCH 077/140] add a clientID 'vscode' to the DAP InitializeRequest --- src/vs/workbench/parts/debug/electron-browser/debugService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 31d8081ee7a..2f2d59f789c 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -682,6 +682,7 @@ export class DebugService implements debug.IDebugService { this.registerSessionListeners(process, session); return session.initialize({ + clientID: 'vscode', adapterID: configuration.type, pathFormat: 'path', linesStartAt1: true, From 255ecfb084be6284b27ac2ab0b5c08d78b883fe5 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Feb 2017 17:08:48 +0100 Subject: [PATCH 078/140] themes as settings --- extensions/theme-defaults/package.json | 5 + src/vs/code/electron-main/window.ts | 23 +- src/vs/code/electron-main/windows.ts | 11 +- .../electron-browser/bootstrap/index.html | 9 +- src/vs/workbench/electron-browser/window.ts | 4 +- .../workbench/parts/html/browser/webview.ts | 6 +- .../parts/scm/electron-browser/scmViewlet.ts | 3 +- .../electron-browser/terminalPanel.ts | 4 +- .../electron-browser/themes.contribution.ts | 34 +- .../themes.test.contribution.ts | 4 +- .../services/themes/common/themeService.ts | 15 +- .../themes/electron-browser/themeService.ts | 324 ++++++++++++------ 12 files changed, 288 insertions(+), 154 deletions(-) diff --git a/extensions/theme-defaults/package.json b/extensions/theme-defaults/package.json index f7e58ccea08..a73e2654255 100644 --- a/extensions/theme-defaults/package.json +++ b/extensions/theme-defaults/package.json @@ -9,26 +9,31 @@ "contributes": { "themes": [ { + "id": "Default Dark+", "label": "Dark+ (default dark)", "uiTheme": "vs-dark", "path": "./themes/dark_plus.json" }, { + "id": "Default Light+", "label": "Light+ (default light)", "uiTheme": "vs", "path": "./themes/light_plus.json" }, { + "id": "Visual Studio Dark", "label": "Dark (Visual Studio)", "uiTheme": "vs-dark", "path": "./themes/dark_vs.json" }, { + "id": "Visual Studio Light", "label": "Light (Visual Studio)", "uiTheme": "vs", "path": "./themes/light_vs.json" }, { + "id": "Default High Contrast", "label": "High Contrast", "uiTheme": "hc-black", "path": "./themes/hc_black.json" diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index c2e21b77c1b..22f0ab90365 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -9,7 +9,6 @@ import * as path from 'path'; import * as platform from 'vs/base/common/platform'; import * as objects from 'vs/base/common/objects'; import nls = require('vs/nls'); -import { IStorageService } from 'vs/code/electron-main/storage'; import { shell, screen, BrowserWindow, systemPreferences, app } from 'electron'; import { TPromise, TValueCallback } from 'vs/base/common/winjs.base'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -78,6 +77,7 @@ export interface IWindowConfiguration extends ParsedArgs { zoomLevel?: number; fullscreen?: boolean; highContrast?: boolean; + baseTheme?: string; accessibilitySupport?: boolean; isInitialStartup?: boolean; @@ -134,8 +134,6 @@ export interface IVSCodeWindow { export class VSCodeWindow implements IVSCodeWindow { - public static colorThemeStorageKey = 'theme'; - private static MIN_WIDTH = 200; private static MIN_HEIGHT = 120; @@ -162,8 +160,7 @@ export class VSCodeWindow implements IVSCodeWindow { config: IWindowCreationOptions, @ILogService private logService: ILogService, @IEnvironmentService private environmentService: IEnvironmentService, - @IConfigurationService private configurationService: IConfigurationService, - @IStorageService private storageService: IStorageService + @IConfigurationService private configurationService: IConfigurationService ) { this.options = config; this._lastFocusTime = -1; @@ -177,9 +174,9 @@ export class VSCodeWindow implements IVSCodeWindow { this.restoreWindowState(config.state); // For VS theme we can show directly because background is white - const themeId = this.storageService.getItem(VSCodeWindow.colorThemeStorageKey); - const usesLightTheme = /vs($| )/.test(themeId); - const usesHighContrastTheme = /hc-black($| )/.test(themeId) || (platform.isWindows && systemPreferences.isInvertedColorScheme()); + const themeId = this.configurationService.lookup('workbench.colorTheme').value; + const usesLightTheme = /^l-/.test(themeId); + const usesHighContrastTheme = /^hc-/.test(themeId) || (platform.isWindows && systemPreferences.isInvertedColorScheme()); // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) const isFullscreenOrMaximized = (this.currentWindowMode === WindowMode.Maximized || this.currentWindowMode === WindowMode.Fullscreen); @@ -505,6 +502,16 @@ export class VSCodeWindow implements IVSCodeWindow { windowConfiguration.highContrast = platform.isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast); windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled(); + // background color + const themeId = this.configurationService.lookup('workbench.colorTheme').value; + if (themeId[0] === 'h') { + windowConfiguration.baseTheme = 'hc-black'; + } else if (themeId[0] === 'l') { + windowConfiguration.baseTheme = 'vs'; + } else { + windowConfiguration.baseTheme = 'vs-dark'; + } + // Perf Counters windowConfiguration.perfStartTime = global.perfStartTime; windowConfiguration.perfAppReady = global.perfAppReady; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index a50e2993692..2f53dc8cc0b 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -289,13 +289,7 @@ export class WindowsManager implements IWindowsMainService { } private onBroadcast(event: string, payload: any): void { - - // Theme changes - if (event === 'vscode:changeColorTheme' && typeof payload === 'string') { - this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload); - } } - public reload(win: VSCodeWindow, cli?: ParsedArgs): void { // Only reload when the window has not vetoed this @@ -530,7 +524,7 @@ export class WindowsManager implements IWindowsMainService { const mru = this.getRecentPathsList(); paths.forEach(p => { - const {path, isFile} = p; + const { path, isFile } = p; if (isFile) { mru.files.unshift(path); @@ -794,8 +788,7 @@ export class WindowsManager implements IWindowsMainService { }, this.logService, this.environmentService, - this.configurationService, - this.storageService + this.configurationService ); WindowsManager.WINDOWS.push(vscodeWindow); diff --git a/src/vs/workbench/electron-browser/bootstrap/index.html b/src/vs/workbench/electron-browser/bootstrap/index.html index 73cc8d4c91d..cd7041e6db5 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.html +++ b/src/vs/workbench/electron-browser/bootstrap/index.html @@ -16,13 +16,8 @@