diff --git a/README.md b/README.md index 91566a493db..5d5ee55c63a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ a code editor with what developers need for their core edit-build-debug cycle. Code provides comprehensive editing and debugging support, an extensibility model, and lightweight integration with existing tools. +VS Code is updated monthly with new features and bug fixes. You can download it for Windows, Mac and Linux on [VS Code's website](https://code.visualstudio.com/Download). To get the latest releases everyday, you can install the [Insiders version of VS Code](https://code.visualstudio.com/insiders). This builds from the master branch and is updated at least daily. +

VS Code in action

diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 11daa6dd82f..404b81bfff5 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -39,8 +39,8 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.9.0' }, - { name: 'ms-vscode.node-debug2', version: '1.9.0' } + { name: 'ms-vscode.node-debug', version: '1.9.1' }, + { name: 'ms-vscode.node-debug2', version: '1.9.1' } ]; const vscodeEntryPoints = _.flatten([ @@ -110,7 +110,7 @@ const config = { version: packageJson.electronVersion, productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2016 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2017 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', diff --git a/build/monaco/api.js b/build/monaco/api.js index 4cab80a45a9..469b19bd79d 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -188,21 +188,21 @@ function format(text) { } function getDefaultOptions() { return { - IndentSize: 4, - TabSize: 4, - NewLineCharacter: '\r\n', - ConvertTabsToSpaces: true, - IndentStyle: ts.IndentStyle.Block, - InsertSpaceAfterCommaDelimiter: true, - InsertSpaceAfterSemicolonInForStatements: true, - InsertSpaceBeforeAndAfterBinaryOperators: true, - InsertSpaceAfterKeywordsInControlFlowStatements: true, - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, - PlaceOpenBraceOnNewLineForFunctions: false, - PlaceOpenBraceOnNewLineForControlBlocks: false, + indentSize: 4, + tabSize: 4, + newLineCharacter: '\r\n', + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Block, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, }; } } diff --git a/build/monaco/api.ts b/build/monaco/api.ts index 2ca2968c6c3..b399d1b1d81 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -217,22 +217,22 @@ function format(text:string): string { function getDefaultOptions(): ts.FormatCodeOptions { return { - IndentSize: 4, - TabSize: 4, - NewLineCharacter: '\r\n', - ConvertTabsToSpaces: true, - IndentStyle: ts.IndentStyle.Block, + indentSize: 4, + tabSize: 4, + newLineCharacter: '\r\n', + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Block, - InsertSpaceAfterCommaDelimiter: true, - InsertSpaceAfterSemicolonInForStatements: true, - InsertSpaceBeforeAndAfterBinaryOperators: true, - InsertSpaceAfterKeywordsInControlFlowStatements: true, - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, - PlaceOpenBraceOnNewLineForFunctions: false, - PlaceOpenBraceOnNewLineForControlBlocks: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, }; } } diff --git a/extensions/html/package.json b/extensions/html/package.json index 3e60a20edab..b7d34877f26 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -141,6 +141,16 @@ "default": true, "description": "%html.suggest.html5.desc%" }, + "html.validate.scripts": { + "type": "boolean", + "default": true, + "description": "%html.validate.scripts%" + }, + "html.validate.styles": { + "type": "boolean", + "default": true, + "description": "%html.validate.styles%" + }, "html.trace.server": { "type": "string", "enum": [ diff --git a/extensions/html/package.nls.json b/extensions/html/package.nls.json index d27f854eed5..f66e083890f 100644 --- a/extensions/html/package.nls.json +++ b/extensions/html/package.nls.json @@ -10,5 +10,7 @@ "html.format.extraLiners.desc": "List of tags, comma separated, that should have an extra newline before them. 'null' defaults to \"head, body, /html\".", "html.suggest.angular1.desc": "Configures if the built-in HTML language support suggests Angular V1 tags and properties.", "html.suggest.ionic.desc": "Configures if the built-in HTML language support suggests Ionic tags, properties and values.", - "html.suggest.html5.desc":"Configures if the built-in HTML language support suggests HTML5 tags, properties and values." + "html.suggest.html5.desc":"Configures if the built-in HTML language support suggests HTML5 tags, properties and values.", + "html.validate.scripts": "Configures if the built-in HTML language support validates embedded scripts.", + "html.validate.styles": "Configures if the built-in HTML language support validates embedded styles." } \ No newline at end of file diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index 4d66f8e4303..68029c1bf67 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -6,9 +6,11 @@ import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType } from 'vscode-languageserver'; import { DocumentContext } from 'vscode-html-languageservice'; -import { TextDocument, Diagnostic, DocumentLink, Range, TextEdit, SymbolInformation } from 'vscode-languageserver-types'; +import { TextDocument, Diagnostic, DocumentLink, Range, SymbolInformation } from 'vscode-languageserver-types'; import { getLanguageModes, LanguageModes } from './modes/languageModes'; +import { format } from './modes/formatting'; + import * as url from 'url'; import * as path from 'path'; import uri from 'vscode-uri'; @@ -69,9 +71,18 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { }; }); +let validation = { + html: true, + css: true, + javascript: true +}; + // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration((change) => { settings = change.settings; + let validationSettings = settings && settings.html && settings.html.validate || {}; + validation.css = validationSettings.styles !== false; + validation.javascript = validationSettings.scripts !== false; languageModes.getAllModes().forEach(m => { if (m.configure) { @@ -115,7 +126,7 @@ function triggerValidation(textDocument: TextDocument): void { function validateTextDocument(textDocument: TextDocument): void { let diagnostics: Diagnostic[] = []; languageModes.getAllModesInDocument(textDocument).forEach(mode => { - if (mode.doValidation) { + if (mode.doValidation && validation[mode.getId()]) { pushAll(diagnostics, mode.doValidation(textDocument)); } }); @@ -201,18 +212,11 @@ connection.onSignatureHelp(signatureHelpParms => { connection.onDocumentRangeFormatting(formatParams => { let document = documents.get(formatParams.textDocument.uri); - let ranges = languageModes.getModesInRange(document, formatParams.range); - let result: TextEdit[] = []; + let unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || ''; - let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/), html: true }; - ranges.forEach(r => { - let mode = r.mode; - if (mode && mode.format && enabledModes[mode.getId()] && !r.attributeValue) { - let edits = mode.format(document, r, formatParams.options); - pushAll(result, edits); - } - }); - return result; + let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) }; + + return format(languageModes, document, formatParams.range, formatParams.options, enabledModes); }); connection.onDocumentLinks(documentLinkParam => { diff --git a/extensions/html/server/src/modes/formatting.ts b/extensions/html/server/src/modes/formatting.ts new file mode 100644 index 00000000000..6213ad71b92 --- /dev/null +++ b/extensions/html/server/src/modes/formatting.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { applyEdits } from '../utils/edits'; +import { TextDocument, Range, TextEdit, FormattingOptions } from 'vscode-languageserver-types'; +import { LanguageModes } from './languageModes'; + +export function format(languageModes: LanguageModes, document: TextDocument, formatRange: Range, formattingOptions: FormattingOptions, enabledModes: { [mode: string]: boolean }) { + // run the html formatter on the full range and pass the result content to the embedded formatters. + // from the final content create a single edit + // advantages of this approach are + // - correct indents in the html document + // - correct initial indent for embedded formatters + // - no worrying of overlapping edits + + // perform a html format and apply changes to a new document + let htmlMode = languageModes.getMode('html'); + let htmlEdits = htmlMode.format(document, formatRange, formattingOptions); + let htmlFormattedContent = applyEdits(document, htmlEdits); + let newDocument = TextDocument.create(document.uri + '.tmp', document.languageId, document.version, htmlFormattedContent); + try { + // run embedded formatters on html formatted content: - formatters see correct initial indent + let afterFormatRangeLength = document.getText().length - document.offsetAt(formatRange.end); // length of unchanged content after replace range + let newFormatRange = Range.create(formatRange.start, newDocument.positionAt(htmlFormattedContent.length - afterFormatRangeLength)); + let embeddedRanges = languageModes.getModesInRange(newDocument, newFormatRange); + + let embeddedEdits: TextEdit[] = []; + + for (let r of embeddedRanges) { + let mode = r.mode; + if (mode && mode.format && enabledModes[mode.getId()] && !r.attributeValue) { + let edits = mode.format(newDocument, r, formattingOptions); + for (let edit of edits) { + embeddedEdits.push(edit); + } + } + }; + + if (embeddedEdits.length === 0) { + return htmlEdits; + } + + // apply all embedded format edits and create a single edit for all changes + let resultContent = applyEdits(newDocument, embeddedEdits); + let resultReplaceText = resultContent.substring(document.offsetAt(formatRange.start), resultContent.length - afterFormatRangeLength); + + return [TextEdit.replace(formatRange, resultReplaceText)]; + } finally { + languageModes.onDocumentRemoved(newDocument); + } + +} \ No newline at end of file diff --git a/extensions/html/server/src/modes/htmlMode.ts b/extensions/html/server/src/modes/htmlMode.ts index ecee841da41..55778a8a7b8 100644 --- a/extensions/html/server/src/modes/htmlMode.ts +++ b/extensions/html/server/src/modes/htmlMode.ts @@ -20,7 +20,7 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM settings = options && options.html; }, doComplete(document: TextDocument, position: Position) { - let options = settings && settings.html && settings.html.suggest; + let options = settings && settings.suggest; return htmlLanguageService.doComplete(document, position, htmlDocuments.get(document), options); }, doHover(document: TextDocument, position: Position) { diff --git a/extensions/html/server/src/test/formatting.test.ts b/extensions/html/server/src/test/formatting.test.ts index 84ea218e779..be6a2a5db69 100644 --- a/extensions/html/server/src/test/formatting.test.ts +++ b/extensions/html/server/src/test/formatting.test.ts @@ -8,6 +8,8 @@ import * as assert from 'assert'; import { getLanguageModes } from '../modes/languageModes'; import { TextDocument, Range, TextEdit, FormattingOptions } from 'vscode-languageserver-types'; +import { format } from '../modes/formatting'; + suite('HTML Embedded Formatting', () => { function assertFormat(value: string, expected: string, options?: any): void { @@ -31,15 +33,8 @@ suite('HTML Embedded Formatting', () => { let range = Range.create(document.positionAt(rangeStartOffset), document.positionAt(rangeEndOffset)); let formatOptions = FormattingOptions.create(2, true); - let ranges = languageModes.getModesInRange(document, range); - let result: TextEdit[] = []; - ranges.forEach(r => { - let mode = r.mode; - if (mode && mode.format) { - let edits = mode.format(document, r, formatOptions); - pushAll(result, edits); - } - }); + let result = format(languageModes, document, range, formatOptions, { css: true, javascript: true }); + let actual = applyEdits(document, result); assert.equal(actual, expected); } @@ -52,38 +47,38 @@ suite('HTML Embedded Formatting', () => { test('HTML & Scripts', function (): any { assertFormat('', '\n\n\n \n\n\n'); - assertFormat('', '\n\n\n \n\n\n'); - assertFormat('', '\n\n\n \n\n\n'); - assertFormat('\n ', '\n\n\n \n\n\n'); - assertFormat('\n ', '\n\n\n \n\n\n'); + assertFormat('', '\n\n\n \n\n\n'); + assertFormat('', '\n\n\n \n\n\n'); + assertFormat('\n ', '\n\n\n \n\n\n'); + assertFormat('\n ', '\n\n\n \n\n\n'); - assertFormat('\n ||', '\n '); + assertFormat('\n ||', '\n '); assertFormat('\n ', '\n '); }); test('Script end tag', function (): any { - assertFormat('\n\n ', '\n\n\n \n\n\n'); + assertFormat('\n\n ', '\n\n\n \n\n\n'); }); test('HTML & Multiple Scripts', function (): any { - assertFormat('\n', '\n\n\n \n\n\n\n'); + assertFormat('\n', '\n\n\n \n \n\n\n'); }); test('HTML & Styles', function (): any { - assertFormat('\n', '\n\n\n \n\n\n'); + assertFormat('\n', '\n\n\n \n\n\n'); }); test('EndWithNewline', function (): any { let options = { html: { format: { - endWithNewline : true + endWithNewline: true } } }; assertFormat('

Hello

', '\n\n\n

Hello

\n\n\n\n', options); assertFormat('|

Hello

|', '\n

Hello

\n', options); - assertFormat('', '\n\n\n \n\n\n\n', options); + assertFormat('', '\n\n\n \n\n\n\n', options); }); }); diff --git a/extensions/html/server/src/utils/edits.ts b/extensions/html/server/src/utils/edits.ts new file mode 100644 index 00000000000..5983d9014fb --- /dev/null +++ b/extensions/html/server/src/utils/edits.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TextDocument, TextEdit, Position } from 'vscode-languageserver-types'; + +export function applyEdits(document: TextDocument, edits: TextEdit[]): string { + let text = document.getText(); + let sortedEdits = edits.sort((a, b) => { + let startDiff = comparePositions(a.range.start, b.range.start); + if (startDiff === 0) { + return comparePositions(a.range.end, b.range.end); + } + return startDiff; + }); + let lastOffset = text.length; + sortedEdits.forEach(e => { + let startOffset = document.offsetAt(e.range.start); + let endOffset = document.offsetAt(e.range.end); + text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length); + lastOffset = startOffset; + }); + return text; +} + +function comparePositions(p1: Position, p2: Position) { + let diff = p2.line - p1.line; + if (diff === 0) { + return p2.character - p1.character; + } + return diff; +} \ No newline at end of file diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage b/extensions/markdown/syntaxes/markdown.tmLanguage index b0982a11087..238afa870ec 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage +++ b/extensions/markdown/syntaxes/markdown.tmLanguage @@ -48,10 +48,10 @@ include - #raw_block - - - include + + + + #fenced_code_block_css @@ -202,6 +202,14 @@ include #fenced_code_block_csharp + + include + #fenced_code_block_unknown + + + include + #raw_block + include #link-def @@ -312,7 +320,29 @@ begin - (^|\G)\s*(?=</?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|pre|p|param|script|section|source|style|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(\s|$|/?>)) + (^|\G)\s*(?=<(script|style|pre)(\s|$|>)) + patterns + + + begin + (\s*|$) + patterns + + + include + text.html.basic + + + while + ^\s*(?!</(script|style|pre)>) + + + end + (?=</(script|style|pre)>) + + + begin + (^|\G)\s*(?=</?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(\s|$|/?>)) patterns @@ -325,7 +355,7 @@ begin - (^|\G)\s*(?=(<[a-zA-Z0-9\-].*>|</[a-zA-Z0-9\-]>)\s*$) + (^|\G)\s*(?=(<[a-zA-Z0-9\-](/?>|\s.*?>)|</[a-zA-Z0-9\-]>)\s*$) patterns @@ -538,156 +568,469 @@ (^|\G)(?!\s*$|#|[ ]{0,3}((([*_][ ]{0,2}\2){2,}[ \t]*$\n?)|([*+-]([ ]{1,3}|\t)))|\s*\[.+?\]:|>) - raw_block - - begin - (^|\G)([ ]{4}|\t) - name - markup.raw.block.markdown - while - (^|\G)([ ]{4}|\t) - fenced_code_block_css begin - (^|\G)\s*(([`~]){3,})\s*(css|css.erb)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((css|css.erb)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + patterns - include - source.css + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.css + + fenced_code_block_basic begin - (^|\G)\s*(([`~]){3,})\s*(html|htm|shtml|xhtml|inc|tmpl|tpl)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((html|htm|shtml|xhtml|inc|tmpl|tpl)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + patterns - include - text.html.basic + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + text.html.basic + + fenced_code_block_ini begin - (^|\G)\s*(([`~]){3,})\s*(ini|conf)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((ini|conf)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.ini + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.ini + + fenced_code_block_java begin - (^|\G)\s*(([`~]){3,})\s*(java|bsh)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((java|bsh)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.java + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.java + + fenced_code_block_lua begin - (^|\G)\s*(([`~]){3,})\s*(lua)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((lua)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.lua + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.lua + + fenced_code_block_makefile begin - (^|\G)\s*(([`~]){3,})\s*(Makefile|makefile|GNUmakefile|OCamlMakefile)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((Makefile|makefile|GNUmakefile|OCamlMakefile)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.makefile + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.makefile + + fenced_code_block_perl begin - (^|\G)\s*(([`~]){3,})\s*(perl|pl|pm|pod|t|PL|psgi|vcl)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((perl|pl|pm|pod|t|PL|psgi|vcl)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.perl + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.perl + + fenced_code_block_r begin - (^|\G)\s*(([`~]){3,})\s*(R|r|s|S|Rprofile)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((R|r|s|S|Rprofile)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.r + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.r + + fenced_code_block_ruby begin - (^|\G)\s*(([`~]){3,})\s*(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.ruby + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.ruby + + @@ -706,477 +1049,1562 @@ scenarios, and also appears to be consistent with the approach that GitHub takes. begin - (^|\G)\s*(([`~]){3,})\s*(php|php3|php4|php5|phpt|phtml|aw|ctp)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((php|php3|php4|php5|phpt|phtml|aw|ctp)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - comment - - Left to its own devices, the PHP grammar will match HTML as a combination of operators - and constants. Therefore, HTML must take precedence over PHP in order to get proper - syntax highlighting. - - include - text.html.basic - - - include - text.html.php#language + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + comment + + + + Left to its own devices, the PHP grammar will match HTML as a combination of operators + and constants. Therefore, HTML must take precedence over PHP in order to get proper + syntax highlighting. + + include + text.html.basic + + + include + text.html.php#language + + fenced_code_block_sql begin - (^|\G)\s*(([`~]){3,})\s*(sql|ddl|dml)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((sql|ddl|dml)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.sql + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.sql + + fenced_code_block_vs_net begin - (^|\G)\s*(([`~]){3,})\s*(vb)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((vb)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.asp.vb.net + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.asp.vb.net + + fenced_code_block_xml begin - (^|\G)\s*(([`~]){3,})\s*(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - text.xml + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + text.xml + + fenced_code_block_xsl begin - (^|\G)\s*(([`~]){3,})\s*(xsl|xslt)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((xsl|xslt)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - text.xml.xsl + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + text.xml.xsl + + fenced_code_block_yaml begin - (^|\G)\s*(([`~]){3,})\s*(yaml|yml)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((yaml|yml)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.yaml + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.yaml + + fenced_code_block_dosbatch begin - (^|\G)\s*(([`~]){3,})\s*(bat|batch)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((bat|batch)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.dosbatch + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.dosbatch + + fenced_code_block_clojure begin - (^|\G)\s*(([`~]){3,})\s*(clj|cljs|clojure)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((clj|cljs|clojure)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.clojure + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.clojure + + fenced_code_block_coffee begin - (^|\G)\s*(([`~]){3,})\s*(coffee|Cakefile|coffee.erb)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((coffee|Cakefile|coffee.erb)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.coffee + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.coffee + + fenced_code_block_c begin - (^|\G)\s*(([`~]){3,})\s*(c|h)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((c|h)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.c + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.c + + fenced_code_block_diff begin - (^|\G)\s*(([`~]){3,})\s*(patch|diff|rej)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((patch|diff|rej)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.diff + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.diff + + fenced_code_block_dockerfile begin - (^|\G)\s*(([`~]){3,})\s*(dockerfile|Dockerfile)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((dockerfile|Dockerfile)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.dockerfile + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.dockerfile + + fenced_code_block_git_commit begin - (^|\G)\s*(([`~]){3,})\s*(COMMIT_EDITMSG|MERGE_MSG)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((COMMIT_EDITMSG|MERGE_MSG)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - text.git-commit + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + text.git-commit + + fenced_code_block_git_rebase begin - (^|\G)\s*(([`~]){3,})\s*(git-rebase-todo)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((git-rebase-todo)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - text.git-rebase + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + text.git-rebase + + fenced_code_block_groovy begin - (^|\G)\s*(([`~]){3,})\s*(groovy|gvy)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((groovy|gvy)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.groovy + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.groovy + + fenced_code_block_jade begin - (^|\G)\s*(([`~]){3,})\s*(jade)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((jade)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - text.jade + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + text.jade + + fenced_code_block_js begin - (^|\G)\s*(([`~]){3,})\s*(js|jsx|javascript)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((js|jsx|javascript)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.js + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.js + + fenced_code_block_js_regexp begin - (^|\G)\s*(([`~]){3,})\s*(regexp)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((regexp)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.js.regexp + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.js.regexp + + fenced_code_block_json begin - (^|\G)\s*(([`~]){3,})\s*(json|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((json|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.json + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.json + + fenced_code_block_less begin - (^|\G)\s*(([`~]){3,})\s*(less)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((less)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.css.less + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.css.less + + fenced_code_block_objc begin - (^|\G)\s*(([`~]){3,})\s*(objectivec|mm|objc|obj-c|m|h)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((objectivec|mm|objc|obj-c|m|h)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.objc + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.objc + + fenced_code_block_perl6 begin - (^|\G)\s*(([`~]){3,})\s*(perl6|p6|pl6|pm6|nqp)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((perl6|p6|pl6|pm6|nqp)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.perl.6 + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.perl.6 + + fenced_code_block_powershell begin - (^|\G)\s*(([`~]){3,})\s*(powershell|ps1|psm1|psd1)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((powershell|ps1|psm1|psd1)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.powershell + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.powershell + + fenced_code_block_python begin - (^|\G)\s*(([`~]){3,})\s*(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.python + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.python + + fenced_code_block_regexp_python begin - (^|\G)\s*(([`~]){3,})\s*(re)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((re)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.regexp.python + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.regexp.python + + fenced_code_block_shell begin - (^|\G)\s*(([`~]){3,})\s*(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.shell + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.shell + + fenced_code_block_ts begin - (^|\G)\s*(([`~]){3,})\s*(typescript|ts)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((typescript|ts)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.ts + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.ts + + fenced_code_block_tsx begin - (^|\G)\s*(([`~]){3,})\s*(tsx)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((tsx)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.tsx + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.tsx + + fenced_code_block_csharp begin - (^|\G)\s*(([`~]){3,})\s*(cs|csharp|c#)(\s+.*)?$ + (^|\G)(\s*)([`~]{3,})\s*((cs|csharp|c#)(\s+.*)?$) name markup.fenced_code.block.markdown - while - (^|\G)(?!\s*\2\3*\s*$) + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns - include - source.cs + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.cs + + + fenced_code_block_unknown + + name + markup.fenced_code.block.markdown + begin + (^|\G)(\s*)([`~]{3,})\s*(.*)?$ + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 4 + + name + fenced_code.block.language + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + + raw_block + + begin + (^|\G)([ ]{4}|\t) + name + markup.raw.block.markdown + while + (^|\G)([ ]{4}|\t) + separator match diff --git a/extensions/markdown/test/colorize-results/test_md.json b/extensions/markdown/test/colorize-results/test_md.json index be3e30c55c0..e02382cc8d3 100644 --- a/extensions/markdown/test/colorize-results/test_md.json +++ b/extensions/markdown/test/colorize-results/test_md.json @@ -1584,8 +1584,8 @@ } }, { - "c": "````application/json", - "t": "markdown.meta.paragraph", + "c": "````", + "t": "block.definition.fenced_code.markdown.markup.punctuation", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1594,9 +1594,20 @@ "hc_black": ".hc-black .token rgb(255, 255, 255)" } }, + { + "c": "application/json", + "r": { + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "hc_black": ".hc-black .token rgb(255, 255, 255)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "light_vs": ".vs .token rgb(0, 0, 0)" + }, + "t": "block.fenced_code.language.markdown.markup" + }, { "c": " { value: [\"or with a mime type\"] }", - "t": "markdown.meta.paragraph", + "t": "block.fenced_code.markdown.markup", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1607,7 +1618,7 @@ }, { "c": "````", - "t": "markdown.meta.paragraph", + "t": "block.definition.fenced_code.markdown.markup.punctuation", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1827,7 +1838,7 @@ }, { "c": "~~~", - "t": "markdown.meta.paragraph", + "t": "block.definition.fenced_code.markdown.markup.punctuation", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1838,7 +1849,7 @@ }, { "c": "// Markdown extra adds un-indented code blocks too", - "t": "markdown.meta.paragraph", + "t": "block.fenced_code.markdown.markup", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1848,74 +1859,8 @@ } }, { - "c": "if (this", - "t": "markdown.meta.paragraph", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": "_", - "t": "definition.italic.markdown.markup.meta.paragraph.punctuation", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.markup.italic rgb(212, 212, 212)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.markup.italic rgb(0, 0, 0)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.markup.italic rgb(212, 212, 212)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.markup.italic rgb(0, 0, 0)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.markup.italic rgb(255, 255, 255)" - } - }, - { - "c": "is", - "t": "italic.markdown.markup.meta.paragraph", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.markup.italic rgb(212, 212, 212)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.markup.italic rgb(0, 0, 0)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.markup.italic rgb(212, 212, 212)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.markup.italic rgb(0, 0, 0)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.markup.italic rgb(255, 255, 255)" - } - }, - { - "c": "_", - "t": "definition.italic.markdown.markup.meta.paragraph.punctuation", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.markup.italic rgb(212, 212, 212)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.markup.italic rgb(0, 0, 0)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.markup.italic rgb(212, 212, 212)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.markup.italic rgb(0, 0, 0)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.markup.italic rgb(255, 255, 255)" - } - }, - { - "c": "more_code == true ", - "t": "markdown.meta.paragraph", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": "&&", - "t": "markdown.meta.other.paragraph.valid-ampersand", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": " !indented) {", - "t": "markdown.meta.paragraph", + "c": "if (this_is_more_code == true && !indented) {", + "t": "block.fenced_code.markdown.markup", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1926,7 +1871,7 @@ }, { "c": " // tild wrapped code blocks, also not indented", - "t": "markdown.meta.paragraph", + "t": "block.fenced_code.markdown.markup", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1937,7 +1882,7 @@ }, { "c": "}", - "t": "markdown.meta.paragraph", + "t": "block.fenced_code.markdown.markup", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -1948,7 +1893,7 @@ }, { "c": "~~~", - "t": "markdown.meta.paragraph", + "t": "block.definition.fenced_code.markdown.markup.punctuation", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -2595,4 +2540,4 @@ "hc_black": ".hc-black .token rgb(255, 255, 255)" } } -] \ No newline at end of file +] diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 201ddf039de..9f30f48b4f4 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -99,6 +99,11 @@ "default": true, "description": "%typescript.check.tscVersion%" }, + "typescript.referencesCodeLens.enabled": { + "type": "boolean", + "default": false, + "description": "%typescript.referencesCodeLens.enabled%" + }, "typescript.tsserver.trace": { "type": "string", "enum": [ diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index 51b9649ab91..1834876a6d9 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -24,5 +24,6 @@ "format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": "Defines space handling after opening and before closing JSX expression braces. Requires TypeScript >= 2.0.6.", "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." + "javascript.validate.enable": "Enable/disable JavaScript validation.", + "typescript.referencesCodeLens.enabled": "Enable/disable the references code lens" } \ No newline at end of file diff --git a/extensions/typescript/src/features/codeActionProvider.ts b/extensions/typescript/src/features/codeActionProvider.ts index e0ff2b69412..51a9aed5a67 100644 --- a/extensions/typescript/src/features/codeActionProvider.ts +++ b/extensions/typescript/src/features/codeActionProvider.ts @@ -5,7 +5,7 @@ 'use strict'; -import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit } from 'vscode'; +import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit, Position } from 'vscode'; import * as Proto from '../protocol'; import { ITypescriptServiceClient } from '../typescriptService'; @@ -46,7 +46,7 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider public provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): Thenable { const file = this.client.asAbsolutePath(document.uri); if (!file) { - return Promise.resolve(null); + return Promise.resolve([]); } const source: Source = { @@ -99,11 +99,29 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider private onCodeAction(source: Source, workspaceEdit: WorkspaceEdit) { workspace.applyEdit(workspaceEdit).then(success => { if (!success) { - return Promise.reject(null); + return Promise.reject(false); } + + let firstEdit: TextEdit | null = null; + for (const [uri, edits] of workspaceEdit.entries()) { + if (uri.fsPath === source.uri.fsPath) { + firstEdit = edits[0]; + break; + } + } + + if (!firstEdit) { + return true; + } + + const newLines = firstEdit.newText.match(/\n/g); + const editedRange = new Range( + new Position(firstEdit.range.start.line, 0), + new Position(firstEdit.range.end.line + 1 + (newLines ? newLines.length : 0), 0)); + // TODO: Workaround for https://github.com/Microsoft/TypeScript/issues/12249 // apply formatting to the source range until TS returns formatted results - return commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, source.range, {}).then((edits: TextEdit[]) => { + return commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, editedRange, {}).then((edits: TextEdit[]) => { if (!edits || !edits.length) { return false; } diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 65a725816fa..71cb92ffd94 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -119,7 +119,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise { if (this.typingsStatus.isAcquiringTypings) { - return Promise.reject({ + return Promise.reject({ label: localize('acquiringTypingsLabel', 'Acquiring typings...'), detail: localize('acquiringTypingsDetail', 'Acquiring typings definitions for IntelliSense.') }); @@ -220,7 +220,6 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP // Don't complete function calls inside of destructive assigments or imports return this.client.execute('quickinfo', args).then(infoResponse => { const info = infoResponse.body; - console.log(info && info.kind); switch (info && info.kind) { case 'var': case 'let': diff --git a/extensions/typescript/src/features/referencesCodeLensProvider.ts b/extensions/typescript/src/features/referencesCodeLensProvider.ts new file mode 100644 index 00000000000..145859cecb2 --- /dev/null +++ b/extensions/typescript/src/features/referencesCodeLensProvider.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CodeLensProvider, CodeLens, CancellationToken, TextDocument, Range, Uri, Location, Position, workspace, WorkspaceConfiguration } from 'vscode'; +import * as Proto from '../protocol'; +import * as PConst from '../protocol.const'; + +import { ITypescriptServiceClient } from '../typescriptService'; + +import * as nls from 'vscode-nls'; +let localize = nls.loadMessageBundle(); + + +class ReferencesCodeLens extends CodeLens { + public document: Uri; + public file: string; + + constructor(document: Uri, file: string, range: Range) { + super(range); + this.document = document; + this.file = file; + } +} + +export default class TypeScriptReferencesCodeLensProvider implements CodeLensProvider { + private client: ITypescriptServiceClient; + private enabled = false; + + constructor(client: ITypescriptServiceClient) { + this.client = client; + } + + public updateConfiguration(config: WorkspaceConfiguration): void { + let typeScriptConfig = workspace.getConfiguration('typescript'); + this.enabled = typeScriptConfig.get('referencesCodeLens.enabled', false); + } + + provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { + if (!this.enabled) { + return Promise.resolve([]); + } + + const filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve([]); + } + return this.client.execute('navtree', { file: filepath }, token).then(response => { + const tree = response.body; + const referenceableSpans: Range[] = []; + if (tree && tree.childItems) { + tree.childItems.forEach(item => this.extractReferenceableSymbols(document, item, referenceableSpans)); + } + return Promise.resolve(referenceableSpans.map(span => new ReferencesCodeLens(document.uri, filepath, span))); + }); + } + + resolveCodeLens(inputCodeLens: CodeLens, token: CancellationToken): Promise { + const codeLens = inputCodeLens as ReferencesCodeLens; + if (!codeLens.document) { + return Promise.reject(codeLens); + } + const args: Proto.FileLocationRequestArgs = { + file: codeLens.file, + line: codeLens.range.start.line + 1, + offset: codeLens.range.start.character + 1 + }; + return this.client.execute('references', args, token).then(response => { + if (response && response.body) { + const referenceCount = Math.max(0, response.body.refs.length - 1); + const locations = response.body.refs.map(reference => + new Location(Uri.file(reference.file), + new Range( + new Position(reference.start.line - 1, reference.start.offset - 1), + new Position(reference.end.line - 1, reference.end.offset - 1)))); + + codeLens.command = { + title: referenceCount + ' ' + (referenceCount === 1 ? localize('oneReferenceLabel', 'reference') : localize('manyReferenceLabel', 'references')), + command: 'editor.action.showReferences', + arguments: [codeLens.document, codeLens.range.start, locations] + }; + return Promise.resolve(codeLens); + } + return Promise.reject(codeLens); + }).catch(() => { + codeLens.command = { + title: localize('referenceErrorLabel', 'Could not determine references'), + command: '' + }; + return Promise.resolve(codeLens); + }); + } + + private extractReferenceableSymbols(document: TextDocument, item: Proto.NavigationTree, results: Range[]) { + if (!item) { + return; + } + + const span = item.spans && item.spans[0]; + if (span) { + const range = new Range( + new Position(span.start.line - 1, span.start.offset - 1), + new Position(span.end.line - 1, span.end.offset - 1)); + + // TODO: TS currently requires the position for 'references 'to be inside of the identifer + // Massage the range to make sure this is the case + const text = document.getText(range); + + switch (item.kind) { + case PConst.Kind.const: + case PConst.Kind.let: + case PConst.Kind.variable: + case PConst.Kind.function: + // Only show references for exported variables + if (!item.kindModifiers.match(/\bexport\b/)) { + break; + } + // fallthrough + + case PConst.Kind.memberFunction: + case PConst.Kind.memberVariable: + case PConst.Kind.memberGetAccessor: + case PConst.Kind.memberSetAccessor: + case PConst.Kind.constructorImplementation: + case PConst.Kind.class: + case PConst.Kind.interface: + case PConst.Kind.type: + case PConst.Kind.enum: + const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${item.text}`, 'g'); + const match = identifierMatch.exec(text); + const start = match ? match.index + match[1].length : 0; + results.push(new Range( + new Position(range.start.line, range.start.character + start), + new Position(range.start.line, range.start.character + start + item.text.length))); + break; + } + } + + (item.childItems || []).forEach(item => this.extractReferenceableSymbols(document, item, results)); + } +}; \ No newline at end of file diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 16080631a3d..02891af3f94 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -35,6 +35,7 @@ import BufferSyncSupport from './features/bufferSyncSupport'; import CompletionItemProvider from './features/completionItemProvider'; import WorkspaceSymbolProvider from './features/workspaceSymbolProvider'; import CodeActionProvider from './features/codeActionProvider'; +import ReferenceCodeLensProvider from './features/referencesCodeLensProvider'; import * as BuildStatus from './utils/buildStatus'; import * as ProjectStatus from './utils/projectStatus'; @@ -107,6 +108,7 @@ class LanguageProvider { private formattingProvider: FormattingProvider; private formattingProviderRegistration: Disposable | null; private typingsStatus: TypingsStatus; + private referenceCodeLensProvider: ReferenceCodeLensProvider; private _validate: boolean; @@ -156,6 +158,12 @@ class LanguageProvider { this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(this.description.modeIds, this.formattingProvider); } + this.referenceCodeLensProvider = new ReferenceCodeLensProvider(client); + this.referenceCodeLensProvider.updateConfiguration(config); + if (client.apiVersion.has206Features()) { + languages.registerCodeLensProvider(this.description.modeIds, this.referenceCodeLensProvider); + } + this.description.modeIds.forEach(modeId => { let selector: DocumentFilter = { scheme: 'file', language: modeId }; languages.registerCompletionItemProvider(selector, this.completionItemProvider, '.'); @@ -171,6 +179,7 @@ class LanguageProvider { if (client.apiVersion.has213Features()) { languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, modeId)); } + languages.setLanguageConfiguration(modeId, { indentationRules: { // ^(.*\*/)?\s*\}.*$ @@ -217,6 +226,9 @@ class LanguageProvider { if (this.completionItemProvider) { this.completionItemProvider.updateConfiguration(config); } + if (this.referenceCodeLensProvider) { + this.referenceCodeLensProvider.updateConfiguration(config); + } if (this.formattingProvider) { this.formattingProvider.updateConfiguration(config); if (!this.formattingProvider.isEnabled() && this.formattingProviderRegistration) { diff --git a/extensions/typescript/src/typescriptServiceClient.ts b/extensions/typescript/src/typescriptServiceClient.ts index ad18e62f4c3..12e36d958e9 100644 --- a/extensions/typescript/src/typescriptServiceClient.ts +++ b/extensions/typescript/src/typescriptServiceClient.ts @@ -346,17 +346,20 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient switch (selected.id) { case MessageAction.useLocal: let pathValue = './node_modules/typescript/lib'; - tsConfig.update('tsdk', pathValue, false); - window.showInformationMessage(localize('updatedtsdk', 'Updated workspace setting \'typescript.tsdk\' to {0}', pathValue)); + tsConfig.update('tsdk', pathValue, false).then( + () => window.showInformationMessage(localize('updatedtsdk', 'Updated workspace setting \'typescript.tsdk\' to {0}', pathValue)), + () => window.showErrorMessage(localize('updateTsdkFailed', 'Could not update the \'typescript.tsdk\' workspace setting. Please check that the workspace settings file is valid'))); showVersionStatusItem = true; return localModulePath; case MessageAction.useBundled: - tsConfig.update(checkWorkspaceVersionKey, false, false); - window.showInformationMessage(localize('updateLocalWorkspaceCheck', 'Updated workspace setting \'typescript.check.workspaceVersion\' to false')); + tsConfig.update(checkWorkspaceVersionKey, false, false).then( + () => window.showInformationMessage(localize('updateLocalWorkspaceCheck', 'Updated workspace setting \'typescript.check.workspaceVersion\' to false')), + () => window.showErrorMessage(localize('updateLocalWorkspaceCheckFailed', 'Could not update the \'typescript.check.workspaceVersion\' workspace setting. Please check that the workspace settings file is valid'))); return modulePath; case MessageAction.neverCheckLocalVersion: - window.showInformationMessage(localize('updateGlobalWorkspaceCheck', 'Updated user setting \'typescript.check.workspaceVersion\' to false')); - tsConfig.update(checkWorkspaceVersionKey, false, true); + tsConfig.update(checkWorkspaceVersionKey, false, true).then( + () => window.showInformationMessage(localize('updateGlobalWorkspaceCheck', 'Updated user setting \'typescript.check.workspaceVersion\' to false')), + () => window.showErrorMessage(localize('updateGlobalWorkspaceCheckFailed', 'Could not update \'typescript.check.workspaceVersion\' user setting. Please check that your user settings file is valid'))); return modulePath; default: return modulePath; diff --git a/package.json b/package.json index 1e5516b581c..387284d3f53 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-oss-dev", "version": "1.9.0", "electronVersion": "1.4.6", - "distro": "2eecc8b68318fba1fc5b62930287914ac9225e8a", + "distro": "ef07477c3bbf2aa2f274b13093cbe0d96fa59fdd", "author": { "name": "Microsoft Corporation" }, @@ -48,6 +48,7 @@ "eslint": "^3.4.0", "event-stream": "^3.1.7", "express": "^4.13.1", + "flatpak-bundler": "^0.1.1", "ghooks": "1.0.3", "glob": "^5.0.13", "gulp": "^3.8.9", @@ -59,6 +60,7 @@ "gulp-cssnano": "^2.1.0", "gulp-filter": "^3.0.0", "gulp-flatmap": "^1.0.0", + "gulp-image-resize": "^0.10.0", "gulp-json-editor": "^2.2.1", "gulp-mocha": "^2.1.3", "gulp-remote-src": "^0.4.0", @@ -71,8 +73,6 @@ "gulp-uglify": "^2.0.0", "gulp-util": "^3.0.6", "gulp-vinyl-zip": "^1.2.2", - "gulp-image-resize": "^0.10.0", - "flatpak-bundler": "^0.1.1", "innosetup-compiler": "^5.5.60", "is": "^3.1.0", "istanbul": "^0.3.17", @@ -91,8 +91,8 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "tslint": "^3.3.0", - "typescript": "2.0.3", - "typescript-formatter": "3.1.0", + "typescript": "^2.1.4", + "typescript-formatter": "4.0.1", "uglify-js": "2.4.8", "underscore": "^1.8.2", "vinyl": "^0.4.5", @@ -116,4 +116,4 @@ "windows-mutex": "^0.2.0", "fsevents": "0.3.8" } -} +} \ No newline at end of file diff --git a/resources/linux/bin/code.sh b/resources/linux/bin/code.sh index 2bc6e635930..463a655a4d2 100755 --- a/resources/linux/bin/code.sh +++ b/resources/linux/bin/code.sh @@ -4,7 +4,6 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # If root, ensure that --user-data-dir is specified -ARGS=$@ if [ "$(id -u)" = "0" ]; then while test $# -gt 0 do @@ -34,5 +33,5 @@ fi ELECTRON="$VSCODE_PATH/@@NAME@@" CLI="$VSCODE_PATH/resources/app/out/cli.js" -ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" $ARGS +ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" exit $? diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index 80d41601b7b..47ed1616faa 100644 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=/usr/share/@@NAME@@/@@NAME@@ %U +Exec=/usr/share/@@NAME@@/@@NAME@@ --new-window-if-not-first %U Icon=@@NAME@@ Type=Application StartupNotify=true diff --git a/src/main.js b/src/main.js index 289e0e4fb15..d45cc975b8d 100644 --- a/src/main.js +++ b/src/main.js @@ -180,6 +180,7 @@ var nodeCachedDataDir = getNodeCachedDataDir().then(function (value) { // Load our code once ready app.once('ready', function () { + global.perfAppReady = Date.now(); var nlsConfig = getNLSConfiguration(); process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); diff --git a/src/vs/base/browser/builder.ts b/src/vs/base/browser/builder.ts index fd1f4e809dd..4f586a24a2e 100644 --- a/src/vs/base/browser/builder.ts +++ b/src/vs/base/browser/builder.ts @@ -692,7 +692,7 @@ export class Builder implements IDisposable { } }; - return this.on(arg1, fn, listenerToUnbindContainer); + return this.on(arg1, fn, listenerToUnbindContainer, useCapture); } /** diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index 3cec88098ba..6da3585864e 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -114,14 +114,14 @@ export interface IDomEvent { (element: EventHandler, type: string, useCapture?: boolean): _Event; } -export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapture?) => { +export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapture?: boolean) => { const fn = e => emitter.fire(e); const emitter = new Emitter({ onFirstListenerAdd: () => { - element.addEventListener(type, fn); + element.addEventListener(type, fn, useCapture); }, onLastListenerRemove: () => { - element.removeEventListener(type, fn); + element.removeEventListener(type, fn, useCapture); } }); diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 2239732614d..e06eedb4d6d 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -91,7 +91,7 @@ function _renderHtml(content: IHTMLContentElement, options: RenderOptions = {}): const renderer = new marked.Renderer(); renderer.image = (href: string, title: string, text: string) => { - let dimensions = []; + let dimensions: string[] = []; if (href) { const splitted = href.split('|').map(s => s.trim()); href = splitted[0]; diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index c5ceceb4cf4..2c36a2f8012 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -7,7 +7,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 { List } from './listWidget'; +import { List, IListOptions } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import Event, { mapEvent } from 'vs/base/common/event'; @@ -67,10 +67,11 @@ export class PagedList { constructor( container: HTMLElement, delegate: IDelegate, - renderers: IPagedRenderer[] + renderers: IPagedRenderer[], + options: IListOptions = {} ) { const pagedRenderers = renderers.map(r => new PagedRenderer>(r, () => this.model)); - this.list = new List(container, delegate, pagedRenderers); + this.list = new List(container, delegate, pagedRenderers, options); } get onFocusChange(): Event> { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index dc2ae29ed42..6fec843ae34 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -240,7 +240,7 @@ export class ListView implements IDisposable { return DOM.addDisposableListener(domNode, type, handler, useCapture); } - private fireScopedEvent(handler: (event: any) => void, index) { + private fireScopedEvent(handler: (event: any) => void, index: number) { if (index < 0) { return; } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 36581beb633..64ab0b7ed48 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -65,7 +65,7 @@ class Trait implements IDisposable { splice(start: number, deleteCount: number, insertCount: number): void { const diff = insertCount - deleteCount; const end = start + deleteCount; - const indexes = []; + const indexes: number[] = []; for (let index of indexes) { if (index >= start && index < end) { @@ -110,13 +110,13 @@ class Trait implements IDisposable { class FocusTrait extends Trait { - constructor(private getElementId: (number) => string) { + constructor(private getElementId: (number: number) => string) { super('focused'); } renderElement(element: T, index: number, container: HTMLElement): void { super.renderElement(element, index, container); - container.setAttribute('role', 'option'); + container.setAttribute('role', 'treeitem'); container.setAttribute('id', this.getElementId(index)); } } @@ -201,6 +201,7 @@ class Controller implements IDisposable { } export interface IListOptions extends IListViewOptions { + ariaLabel?: string; } const DefaultOptions: IListOptions = {}; @@ -245,13 +246,17 @@ export class List implements IDisposable { }); this.view = new ListView(container, delegate, renderers, options); - this.view.domNode.setAttribute('role', 'listbox'); + this.view.domNode.setAttribute('role', 'tree'); this.view.domNode.tabIndex = 0; this.controller = new Controller(this, this.view); this.disposables = [this.focus, this.selection, this.view, this.controller]; this._onDOMFocus = domEvent(this.view.domNode, 'focus'); this.onFocusChange(this._onFocusChange, this, this.disposables); + + if (options.ariaLabel) { + this.view.domNode.setAttribute('aria-label', options.ariaLabel); + } } splice(start: number, deleteCount: number, ...elements: T[]): void { @@ -418,7 +423,16 @@ export class List implements IDisposable { } private _onFocusChange(): void { - DOM.toggleClass(this.view.domNode, 'element-focused', this.focus.get().length > 0); + const focus = this.focus.get(); + + if (focus.length > 0) { + this.view.domNode.setAttribute('aria-activedescendant', this.getElementId(focus[0])); + } else { + this.view.domNode.removeAttribute('aria-activedescendant'); + } + + this.view.domNode.setAttribute('role', 'tree'); + DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0); } dispose(): void { diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index 5a2687d5055..0c733246563 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -25,22 +25,22 @@ cursor: default !important; } -.vertical-cursor-container * { +.vertical-cursor-container { cursor: ew-resize; } -.horizontal-cursor-container * { +.horizontal-cursor-container { cursor: ns-resize; } /** Custom Mac Cursor */ .monaco-sash.mac.vertical, -.vertical-cursor-container-mac * { +.vertical-cursor-container-mac { cursor: col-resize; } .monaco-sash.mac.horizontal, -.horizontal-cursor-container-mac * { +.horizontal-cursor-container-mac { cursor: row-resize; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/timer/timer.css b/src/vs/base/browser/ui/timer/timer.css deleted file mode 100644 index 9f3515b0d95..00000000000 --- a/src/vs/base/browser/ui/timer/timer.css +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -.benchmarktimerbox { - z-index: 100; - position: absolute; - color: black; - background: lightblue; - top: 100px; - right: 20px; - font-family: monospace; -} - -.benchmarktimerbox .inner { - width: 600px; - height: 300px; - overflow: scroll; -} - -.benchmarktimerbox .timeFilter { - width: 50px; -} - -.benchmarktimerbox pre { - margin: 0; -} - -.timer-event-1 { background: rgba(190, 191, 193, 0.4); color: black; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/timer/timer.ts b/src/vs/base/browser/ui/timer/timer.ts deleted file mode 100644 index 1b19e91e145..00000000000 --- a/src/vs/base/browser/ui/timer/timer.ts +++ /dev/null @@ -1,281 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 'vs/css!./timer'; -import { TimeKeeper, ITimerEvent, getTimeKeeper } from 'vs/base/common/timer'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import DomUtils = require('vs/base/browser/dom'); - -export class TimeKeeperRenderer { - - private listenersToRemove: IDisposable[]; - private timeKeeper: TimeKeeper; - private outerDomNode: HTMLElement; - private domNode: HTMLElement; - private renderCnt: number; - private lastEventIndex: number; - - private textFilter: string; - private textFilterDomNode: HTMLInputElement; - private timeFilter: number; - private timeFilterDomNode: HTMLInputElement; - private intervalTokenId: number; - - private renderedEvents: { - [key: string]: ITimerEvent; - }; - - private onHide: () => void; - - constructor(onHide: () => void) { - this.timeKeeper = getTimeKeeper(); - this.onHide = onHide; - this.lastEventIndex = 0; - this.renderedEvents = {}; - this.renderCnt = 0; - this.listenersToRemove = []; - this.domNode = this._createDomNode(); - this.intervalTokenId = window.setInterval(() => this._render(), 500); - } - - public destroy(): void { - document.body.removeChild(this.outerDomNode); - window.clearInterval(this.intervalTokenId); - this.listenersToRemove = dispose(this.listenersToRemove); - } - - private _createDomNode(): HTMLElement { - this.outerDomNode = document.createElement('div'); - this.outerDomNode.className = 'benchmarktimerbox'; - - // Clear - let cancel: HTMLInputElement = document.createElement('input'); - cancel.type = 'button'; - cancel.value = 'Clear'; - this.listenersToRemove.push(DomUtils.addDisposableListener(cancel, 'click', () => this._onClear())); - this.outerDomNode.appendChild(cancel); - - // Text filter - this.textFilterDomNode = document.createElement('input'); - this.textFilterDomNode.type = 'text'; - this.textFilterDomNode.className = 'textFilter'; - this.listenersToRemove.push(DomUtils.addDisposableListener(this.textFilterDomNode, 'keydown', () => this.onTextFilterChange())); - this.textFilter = ''; - this.outerDomNode.appendChild(document.createTextNode('Filter')); - this.outerDomNode.appendChild(this.textFilterDomNode); - - // Time filter - this.timeFilterDomNode = document.createElement('input'); - this.timeFilterDomNode.type = 'text'; - this.timeFilterDomNode.value = '0'; - this.timeFilterDomNode.className = 'timeFilter'; - this.listenersToRemove.push(DomUtils.addDisposableListener(this.timeFilterDomNode, 'keydown', () => this.onTimeFilterChange())); - this.timeFilter = 0; - this.outerDomNode.appendChild(document.createTextNode('Hide time under')); - this.outerDomNode.appendChild(this.timeFilterDomNode); - - let hide: HTMLInputElement = document.createElement('input'); - hide.type = 'button'; - hide.value = 'Close'; - this.listenersToRemove.push(DomUtils.addDisposableListener(hide, 'click', () => { - this.onHide(); - })); - this.outerDomNode.appendChild(hide); - - let heading = document.createElement('pre'); - heading.appendChild(document.createTextNode(this.renderRow('TOPIC', 'NAME', 'TOOK', 'START', 'END'))); - this.outerDomNode.appendChild(heading); - this.outerDomNode.appendChild(document.createElement('hr')); - - let domNode = document.createElement('div'); - domNode.className = 'inner'; - this.outerDomNode.appendChild(domNode); - - document.body.appendChild(this.outerDomNode); - - return domNode; - } - - private onTextFilterChange(): void { - setTimeout(() => { - this.refilter(); - }); - } - - private onTimeFilterChange(): void { - setTimeout(() => { - this.refilter(); - }); - } - - private matchesTextFilter(event: ITimerEvent): boolean { - if (!this.textFilter) { - return true; - } - if (event.topic.toLowerCase().indexOf(this.textFilter.toLowerCase()) >= 0) { - return true; - } - if (event.name.toLowerCase().indexOf(this.textFilter.toLowerCase()) >= 0) { - return true; - } - return false; - } - - private matchesTimeFilter(event: ITimerEvent): boolean { - if (!this.timeFilter) { - return true; - } - if (event.timeTaken() >= this.timeFilter) { - return true; - } - return false; - } - - private shouldShow(event: ITimerEvent): boolean { - return this.matchesTextFilter(event) && this.matchesTimeFilter(event); - } - - private refilter(): void { - this.textFilter = this.textFilterDomNode.value; - this.timeFilter = parseInt(this.timeFilterDomNode.value, 10); - - let domNodes = Array.prototype.slice.call(this.domNode.children, 0); - for (let i = 0; i < domNodes.length; i++) { - let eventId = domNodes[i].getAttribute('data-event-id'); - let event = this.renderedEvents[eventId]; - - if (this.shouldShow(event)) { - domNodes[i].style.display = 'inherit'; - } else { - domNodes[i].style.display = 'none'; - } - } - } - - private _onClear(): void { - this.lastEventIndex = this.timeKeeper.getCollectedEvents().length; - this.renderedEvents = {}; - this.renderCnt = 0; - DomUtils.clearNode(this.domNode); - } - - private leftPaddedString(size: number, padChar: string, str: string): string { - let spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); - return spaces + str; - } - - private rightPaddedString(size: number, padChar: string, str: string): string { - let spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); - return str + spaces; - } - - private renderRow(topic: string, name: string, timeTook: string, timeStart: string, timerEnd: string): string { - let result = ' '; - result += this.rightPaddedString(10, ' ', topic); - result += this.rightPaddedString(30, ' ', name); - result += ' ' + this.leftPaddedString(15, ' ', timeTook); - result += ' ' + this.leftPaddedString(13, ' ', timeStart); - return result; - } - - private _suffix0(s: string): string { - if (s.charAt(s.length - 3) === '.') { - return s; - } - if (s.charAt(s.length - 2) === '.') { - return s + '0'; - } - return s + '.00'; - } - - private _twoPrecision(a: number): string { - return this._suffix0(Math.round(a * 100) / 100 + ''); - } - - private _absoluteTime(t: number): string { - if (t < 1000) { - return this._twoPrecision(t) + ' ms'; - } - t /= 1000; - if (t < 60) { - return this._twoPrecision(t) + ' s'; - } - t /= 60; - if (t < 60) { - return this._twoPrecision(t) + ' m'; - } - t /= 60; - return this._twoPrecision(t) + ' h'; - } - - private _renderEvent(domNode: HTMLElement, event: ITimerEvent): void { - let start = event.startTime.getTime() - TimeKeeper.PARSE_TIME.getTime(); - - let result = this.renderRow( - event.topic, - event.name, - this._twoPrecision(event.timeTaken()), - this._absoluteTime(start) + '', - this._absoluteTime(start + event.timeTaken()) - ); - domNode.textContent = ''; - domNode.appendChild(document.createTextNode(result)); - } - - private _renderStartTimerEvent(event: ITimerEvent): void { - let domNode = document.createElement('pre'); - this._renderEvent(domNode, event); - this.domNode.appendChild(domNode); - let idString = event.id.toString(); - - domNode.setAttribute('data-event-id', idString); - domNode.className = 'timer-event-' + (event.id % 2); - this.renderedEvents[idString] = event; - - if (this.shouldShow(this.renderedEvents[idString])) { - domNode.style.display = 'inherit'; - } else { - domNode.style.display = 'none'; - } - - this.renderCnt++; - } - - private _render(): void { - let allEvents = this.timeKeeper.getCollectedEvents(), didSomething = false; - - for (let i = this.lastEventIndex; i < allEvents.length; i++) { - let ev = allEvents[i]; - - if (!ev.stopTime) { - // This event is not yet finished => block - this.lastEventIndex = i; - if (didSomething) { - this.domNode.scrollTop = 100000; - } - return; - } - - this._renderStartTimerEvent(ev); - didSomething = true; - } - - if (didSomething) { - this.domNode.scrollTop = 100000; - } - this.lastEventIndex = allEvents.length; - } - - private _repeatStr(str: string, cnt: number): string { - let r = ''; - for (let i = 0; i < cnt; i++) { - r += str; - } - return r; - } -} - diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 2eb260b91bb..176cc70a501 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -18,7 +18,7 @@ export function equals(one: T[], other: T[], itemEquals: (a: T, b: T) => bool return false; } - for (var i = 0, len = one.length; i < len; i++) { + for (let i = 0, len = one.length; i < len; i++) { if (!itemEquals(one[i], other[i])) { return false; } @@ -177,7 +177,7 @@ export function first(array: T[], fn: (item: T) => boolean, notFoundValue: T export function commonPrefixLength(one: T[], other: T[], equals: (a: T, b: T) => boolean = (a, b) => a === b): number { let result = 0; - for (var i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) { + for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) { result++; } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 55442230b02..fadef1d89ea 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -71,8 +71,8 @@ export interface ITask { * The throttler implements this via the queue() method, by providing it a task * factory. Following the example: * - * var throttler = new Throttler(); - * var letters = []; + * const throttler = new Throttler(); + * const letters = []; * * function deliver() { * const lettersToDeliver = letters; @@ -166,8 +166,8 @@ export class SimpleThrottler { * to be executed and the waiting period (delay) must be passed in as arguments. Following * the example: * - * var delayer = new Delayer(WAITING_PERIOD); - * var letters = []; + * const delayer = new Delayer(WAITING_PERIOD); + * const letters = []; * * function letterReceived(l) { * letters.push(l); @@ -402,7 +402,7 @@ export function sequence(promiseFactories: ITask>[]): TPromise(promiseFactories: ITask>[], shouldStop: (t: T) => boolean = t => !!t): TPromise { promiseFactories = [...promiseFactories.reverse()]; - const loop = () => { + const loop: () => TPromise = () => { if (promiseFactories.length === 0) { return TPromise.as(null); } diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index a532c64cc86..bbc24c4aa3b 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -65,12 +65,12 @@ function hsla2rgba(hsla: HSLA): RGBA { let s = Math.min(hsla.s, 1); let l = Math.min(hsla.l, 1); let a = hsla.a === void 0 ? hsla.a : 1; - let r, g, b; + let r: number, g: number, b: number; if (s === 0) { r = g = b = l; // achromatic } else { - let hue2rgb = function hue2rgb(p, q, t) { + let hue2rgb = function hue2rgb(p: number, q: number, t: number) { if (t < 0) { t += 1; } @@ -115,7 +115,7 @@ export class Color { * Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white. */ public getLuminosity(): number { - let luminosityFor = function (color): number { + let luminosityFor = function (color: number): number { let c = color / 255; return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4); }; diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index a90bc8f98a5..7a77f168718 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -83,8 +83,8 @@ export function compareByPrefix(one: string, other: string, lookFor: string): nu } export interface IScorableResourceAccessor { - getLabel(T): string; - getResourcePath(T): string; + getLabel(t: T): string; + getResourcePath(t: T): string; } export function compareByScore(elementA: T, elementB: T, accessor: IScorableResourceAccessor, lookFor: string, lookForNormalizedLower: string, scorerCache?: { [key: string]: number }): number { diff --git a/src/vs/base/common/dates.ts b/src/vs/base/common/dates.ts deleted file mode 100644 index 8a442c17f1a..00000000000 --- a/src/vs/base/common/dates.ts +++ /dev/null @@ -1,38 +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 nls = require('vs/nls'); - -export function since(date: Date): string { - var seconds = (new Date().getTime() - date.getTime()) / 1000; - if (seconds < 60) { - return nls.localize('diff.seconds.verbose', "just now"); - } - - var minutes = seconds / 60; - if (minutes < 60) { - return Math.floor(minutes) === 1 ? nls.localize('diff.minute.verbose', "1 minute ago") : nls.localize('diff.minutes.verbose', "{0} minutes ago", Math.floor(minutes)); - } - - var hours = minutes / 60; - if (hours < 24) { - return Math.floor(hours) === 1 ? nls.localize('diff.hour.verbose', "1 hour ago") : nls.localize('diff.hours.verbose', "{0} hours ago", Math.floor(hours)); - } - - var days = hours / 24; - if (Math.floor(days) === 1) { - return nls.localize('diff.days.yesterday', "yesterday"); - } - - if (days > 6 && days < 8) { - return nls.localize('diff.days.week', "a week ago"); - } - - if (days > 30 && days < 40) { - return nls.localize('diff.days.month', "a month ago"); - } - - return nls.localize('diff.days.verbose', "{0} days ago", Math.floor(days)); -} \ No newline at end of file diff --git a/src/vs/base/common/decorators.ts b/src/vs/base/common/decorators.ts index d20f57e96c3..e7330957a46 100644 --- a/src/vs/base/common/decorators.ts +++ b/src/vs/base/common/decorators.ts @@ -23,7 +23,7 @@ export function memoize(target: any, key: string, descriptor: any) { const memoizeKey = `$memoize$${key}`; - descriptor[fnKey] = function (...args) { + descriptor[fnKey] = function (...args: any[]) { if (!this.hasOwnProperty(memoizeKey)) { Object.defineProperty(this, memoizeKey, { configurable: false, diff --git a/src/vs/base/common/diagnostics.ts b/src/vs/base/common/diagnostics.ts index dc866d18614..9c0e4f6df8f 100644 --- a/src/vs/base/common/diagnostics.ts +++ b/src/vs/base/common/diagnostics.ts @@ -19,7 +19,7 @@ globals.Monaco.Diagnostics = {}; var switches = globals.Monaco.Diagnostics; var map = {}; -var data = []; +var data: any[] = []; function fifo(array: any[], size: number) { while (array.length > size) { diff --git a/src/vs/base/common/diff/diff2.ts b/src/vs/base/common/diff/diff2.ts index c37b16e49e5..91a92308335 100644 --- a/src/vs/base/common/diff/diff2.ts +++ b/src/vs/base/common/diff/diff2.ts @@ -148,8 +148,8 @@ export class LcsDiff2 { // Construct the changes let i = 0; let j = 0; - let xChangeStart, yChangeStart; - let changes = []; + let xChangeStart: number, yChangeStart: number; + let changes: DiffChange[] = []; while (i < xLength && j < yLength) { if (this.resultX[i] && this.resultY[j]) { // No change diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 9f9d99f83d1..926667f2a24 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -218,7 +218,7 @@ export function once(event: Event): Event { } export function any(...events: Event[]): Event { - let listeners = []; + let listeners: IDisposable[] = []; const emitter = new Emitter({ onFirstListenerAdd() { @@ -297,7 +297,7 @@ export class EventBufferer { } bufferEvents(fn: () => void): void { - const buffer = []; + const buffer: Function[] = []; this.buffers.push(buffer); fn(); this.buffers.pop(); @@ -334,7 +334,7 @@ class ChainableEvent implements IChainableEvent { return new ChainableEvent(filterEvent(this._event, fn)); } - on(listener, thisArgs, disposables) { + on(listener, thisArgs, disposables: IDisposable[]) { return this._event(listener, thisArgs, disposables); } } diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index ad0bc3ef508..128f64faa1b 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -209,7 +209,7 @@ function analyzeCamelCaseWord(word: string): ICamelCaseAnalysis { } function isUpperCaseWord(analysis: ICamelCaseAnalysis): boolean { - const { upperPercent, lowerPercent, alphaPercent, numericPercent } = analysis; + const { upperPercent, lowerPercent } = analysis; return lowerPercent === 0 && upperPercent > 0.6; } @@ -272,10 +272,11 @@ export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] } // Matches beginning of words supporting non-ASCII languages -// E.g. "gp" or "g p" will match "Git: Pull" +// If `contiguous` is true then matches word with beginnings of the words in the target. E.g. "pul" will match "Git: Pull" +// Otherwise also matches sub string of the word with beginnings of the words in the target. E.g. "gp" or "g p" will match "Git: Pull" // Useful in cases where the target is words (e.g. command labels) -export function matchesWords(word: string, target: string): IMatch[] { +export function matchesWords(word: string, target: string, contiguous: boolean = false): IMatch[] { if (!target || target.length === 0) { return null; } @@ -283,14 +284,14 @@ export function matchesWords(word: string, target: string): IMatch[] { let result: IMatch[] = null; let i = 0; - while (i < target.length && (result = _matchesWords(word.toLowerCase(), target, 0, i)) === null) { + while (i < target.length && (result = _matchesWords(word.toLowerCase(), target, 0, i, contiguous)) === null) { i = nextWord(target, i + 1); } return result; } -function _matchesWords(word: string, target: string, i: number, j: number): IMatch[] { +function _matchesWords(word: string, target: string, i: number, j: number, contiguous: boolean): IMatch[] { if (i === word.length) { return []; } else if (j === target.length) { @@ -298,12 +299,14 @@ function _matchesWords(word: string, target: string, i: number, j: number): IMat } else if (word[i] !== target[j].toLowerCase()) { return null; } else { - let result = null; + let result: IMatch[] = null; let nextWordIndex = j + 1; - result = _matchesWords(word, target, i + 1, j + 1); - while (!result && (nextWordIndex = nextWord(target, nextWordIndex)) < target.length) { - result = _matchesWords(word, target, i + 1, nextWordIndex); - nextWordIndex++; + result = _matchesWords(word, target, i + 1, j + 1, contiguous); + if (!contiguous) { + while (!result && (nextWordIndex = nextWord(target, nextWordIndex)) < target.length) { + result = _matchesWords(word, target, i + 1, nextWordIndex, contiguous); + nextWordIndex++; + } } return result === null ? null : join({ start: j, end: j + 1 }, result); } diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts index 62ba0371429..a22159dfc85 100644 --- a/src/vs/base/common/json.ts +++ b/src/vs/base/common/json.ts @@ -42,7 +42,7 @@ export interface JSONScanner { /** * Sets the scan position to a new offset. A call to 'scan' is needed to get the first token. */ - setPosition(pos: number); + setPosition(pos: number): void; /** * Read the next token. Returns the tolen code. */ diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 7f24172f50c..415b949b75d 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { isArray } from './types'; - export const empty: IDisposable = Object.freeze({ dispose() { } }); @@ -14,17 +12,24 @@ export interface IDisposable { dispose(): void; } -export function dispose(...disposables: T[]): T; +export function dispose(disposable: T): T; +export function dispose(...disposables: T[]): T[]; export function dispose(disposables: T[]): T[]; -export function dispose(...disposables: T[]): T[] { - const first = disposables[0]; +export function dispose(first: T | T[], ...rest: T[]): T | T[] { - if (isArray(first)) { - disposables = first as any as T[]; + if (Array.isArray(first)) { + first.forEach(d => d && d.dispose()); + return []; + } else if (rest.length === 0) { + if (first) { + first.dispose(); + return first; + } + } else { + dispose(first); + dispose(rest); + return []; } - - disposables.forEach(d => d && d.dispose()); - return []; } export function combinedDisposable(disposables: IDisposable[]): IDisposable { @@ -105,4 +110,4 @@ export abstract class ReferenceCollection { export class ImmortalReference implements IReference { constructor(public object: T) { } dispose(): void { /* noop */ } -} \ No newline at end of file +} diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 2e57ca4cbc1..0eb5f89db37 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -52,7 +52,7 @@ export class LinkedMap { } public keys(): K[] { - var keys: K[] = []; + const keys: K[] = []; for (let key in this.map) { keys.push(this.map[key].key); } @@ -60,7 +60,7 @@ export class LinkedMap { } public values(): T[] { - var values: T[] = []; + const values: T[] = []; for (let key in this.map) { values.push(this.map[key].value); } @@ -68,7 +68,7 @@ export class LinkedMap { } public entries(): Entry[] { - var entries: Entry[] = []; + const entries: Entry[] = []; for (let key in this.map) { entries.push(this.map[key]); } @@ -310,7 +310,7 @@ class Node { */ export class TrieMap { - static PathSplitter = s => s.split(/[\\/]/).filter(s => !!s); + static PathSplitter = (s: string) => s.split(/[\\/]/).filter(s => !!s); private _splitter: (s: string) => string[]; private _root = new Node(); diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index d47010f4e0b..f05d9576d9b 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -143,7 +143,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText let patternMatch: ITextMimeAssociationItem; let extensionMatch: ITextMimeAssociationItem; - for (var i = 0; i < associations.length; i++) { + for (let i = 0; i < associations.length; i++) { let association = associations[i]; // First exact name match @@ -243,7 +243,7 @@ export function isUnspecific(mime: string[] | string): boolean { } export function suggestFilename(langId: string, prefix: string): string { - for (var i = 0; i < registeredAssociations.length; i++) { + for (let i = 0; i < registeredAssociations.length; i++) { let association = registeredAssociations[i]; if (association.userConfigured) { continue; // only support registered ones diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index ee18a5ec9d2..2bd0f2a3d45 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -12,23 +12,23 @@ export namespace Schemas { * A schema that is used for models that exist in memory * only and that have no correspondence on a server or such. */ - export var inMemory: string = 'inmemory'; + export const inMemory: string = 'inmemory'; /** * A schema that is used for setting files */ - export var vscode: string = 'vscode'; + export const vscode: string = 'vscode'; /** * A schema that is used for internal private files */ - export var internal: string = 'private'; + export const internal: string = 'private'; - export var http: string = 'http'; + export const http: string = 'http'; - export var https: string = 'https'; + export const https: string = 'https'; - export var file: string = 'file'; + export const file: string = 'file'; } export interface IXHROptions { diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/paths.ts index 084eacd9782..92de20b38ef 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/paths.ts @@ -11,12 +11,12 @@ import { CharCode } from 'vs/base/common/charCode'; /** * The forward slash path separator. */ -export var sep = '/'; +export const sep = '/'; /** * The native path separator depending on the OS. */ -export var nativeSep = isWindows ? '\\' : '/'; +export const nativeSep = isWindows ? '\\' : '/'; export function relative(from: string, to: string): string { const originalNormalizedFrom = normalize(from); @@ -50,7 +50,7 @@ export function relative(from: string, to: string): string { * @returns the directory name of a path. */ export function dirname(path: string): string { - var idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); + const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); if (idx === 0) { return '.'; } else if (~idx === 0) { @@ -64,7 +64,7 @@ export function dirname(path: string): string { * @returns the base name of a path. */ export function basename(path: string): string { - var idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); + const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); if (idx === 0) { return path; } else if (~idx === path.length - 1) { @@ -79,7 +79,7 @@ export function basename(path: string): string { */ export function extname(path: string): string { path = basename(path); - var idx = ~path.lastIndexOf('.'); + const idx = ~path.lastIndexOf('.'); return idx ? path.substring(~idx) : ''; } diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 3075beb0a97..3ba367b9b62 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -13,8 +13,8 @@ let _isRootUser = false; let _isNative = false; let _isWeb = false; let _isQunit = false; -let _locale = undefined; -let _language = undefined; +let _locale: string = undefined; +let _language: string = undefined; interface NLSConfig { locale: string; @@ -124,7 +124,7 @@ interface IGlobals { clearTimeout(token: TimeoutToken): void; setInterval(callback: (...args: any[]) => void, delay: number, ...args: any[]): IntervalToken; - clearInterval(token: IntervalToken); + clearInterval(token: IntervalToken): void; } const _globals = (typeof self === 'object' ? self : global); diff --git a/src/vs/base/common/stopwatch.ts b/src/vs/base/common/stopwatch.ts index 4f0fc157da3..fe3889b2918 100644 --- a/src/vs/base/common/stopwatch.ts +++ b/src/vs/base/common/stopwatch.ts @@ -6,7 +6,7 @@ import { globals } from 'vs/base/common/platform'; -var hasPerformanceNow = (globals.performance && typeof globals.performance.now === 'function'); +const hasPerformanceNow = (globals.performance && typeof globals.performance.now === 'function'); export class StopWatch { diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index f8b5625a737..fb4c42cd452 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -607,8 +607,8 @@ export function safeBtoa(str: string): string { } export function repeat(s: string, count: number): string { - var result = ''; - for (var i = 0; i < count; i++) { + let result = ''; + for (let i = 0; i < count; i++) { result += s; } return result; diff --git a/src/vs/base/common/timer.ts b/src/vs/base/common/timer.ts deleted file mode 100644 index 59e00729368..00000000000 --- a/src/vs/base/common/timer.ts +++ /dev/null @@ -1,293 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 Platform = require('vs/base/common/platform'); -import errors = require('vs/base/common/errors'); -import precision = require('vs/base/common/stopwatch'); -import { IDisposable } from 'vs/base/common/lifecycle'; - -export var ENABLE_TIMER = false; -var msWriteProfilerMark = Platform.globals['msWriteProfilerMark']; - -export enum Topic { - EDITOR, - LANGUAGES, - WORKER, - WORKBENCH, - STARTUP -} - -export interface ITimerEvent { - id: number; - topic: string; - name: string; - description: string; - data: any; - - startTime: Date; - stopTime: Date; - - stop(stopTime?: Date): void; - timeTaken(): number; -} - -export interface IExistingTimerEvent { - topic: string; - name: string; - - description?: string; - - startTime: Date; - stopTime: Date; -} - -class NullTimerEvent implements ITimerEvent { - public id: number; - public topic: string; - public name: string; - public description: string; - public data: any; - - public startTime: Date; - public stopTime: Date; - - public stop(): void { - return; - } - - public timeTaken(): number { - return -1; - } -} - -class TimerEvent implements ITimerEvent { - public id: number; - public topic: string; - public name: string; - public description: string; - public data: any; - - public startTime: Date; - public stopTime: Date; - - private timeKeeper: TimeKeeper; - private sw: precision.StopWatch; - - constructor(timeKeeper: TimeKeeper, name: string, topic: string, startTime?: Date, description?: string) { - this.timeKeeper = timeKeeper; - this.name = name; - this.description = description; - this.topic = topic; - this.stopTime = null; - - if (startTime) { - this.startTime = startTime; - return; - } - - this.startTime = new Date(); - this.sw = precision.StopWatch.create(); - - if (msWriteProfilerMark) { - var profilerName = ['Monaco', this.topic, this.name, 'start']; - msWriteProfilerMark(profilerName.join('|')); - } - } - - public stop(stopTime?: Date): void { - - // already stopped - if (this.stopTime !== null) { - return; - } - - if (stopTime) { - this.stopTime = stopTime; - this.sw = null; - this.timeKeeper._onEventStopped(this); - return; - } - - this.stopTime = new Date(); - if (this.sw) { - this.sw.stop(); - } - - this.timeKeeper._onEventStopped(this); - - if (msWriteProfilerMark) { - var profilerName = ['Monaco', this.topic, this.name, 'stop']; - msWriteProfilerMark(profilerName.join('|')); - } - } - - public timeTaken(): number { - if (this.sw) { - return this.sw.elapsed(); - } - if (this.stopTime) { - return this.stopTime.getTime() - this.startTime.getTime(); - } - return -1; - } -} - -export interface IEventsListener { - (events: ITimerEvent[]): void; -} - -export class TimeKeeper { - /** - * After being started for 1 minute, all timers are automatically stopped. - */ - private static _MAX_TIMER_LENGTH = 60000; // 1 minute - /** - * Every 2 minutes, a sweep of current started timers is done. - */ - private static _CLEAN_UP_INTERVAL = 120000; // 2 minutes - /** - * Collect at most 1000 events. - */ - private static _EVENT_CACHE_LIMIT = 1000; - - private static EVENT_ID = 1; - public static PARSE_TIME = new Date(); - - - private cleaningIntervalId: Platform.IntervalToken; - private collectedEvents: ITimerEvent[]; - private listeners: IEventsListener[]; - - constructor() { - this.cleaningIntervalId = -1; - this.collectedEvents = []; - this.listeners = []; - } - - public isEnabled(): boolean { - return ENABLE_TIMER; - } - - public start(topic: Topic | string, name: string, start?: Date, description?: string): ITimerEvent { - if (!this.isEnabled()) { - return nullEvent; - } - - var strTopic: string; - - if (typeof topic === 'string') { - strTopic = topic; - } else if (topic === Topic.EDITOR) { - strTopic = 'Editor'; - } else if (topic === Topic.LANGUAGES) { - strTopic = 'Languages'; - } else if (topic === Topic.WORKER) { - strTopic = 'Worker'; - } else if (topic === Topic.WORKBENCH) { - strTopic = 'Workbench'; - } else if (topic === Topic.STARTUP) { - strTopic = 'Startup'; - } - - this.initAutoCleaning(); - var event = new TimerEvent(this, name, strTopic, start, description); - this.addEvent(event); - - return event; - } - - public dispose(): void { - if (this.cleaningIntervalId !== -1) { - Platform.clearInterval(this.cleaningIntervalId); - this.cleaningIntervalId = -1; - } - } - - public addListener(listener: IEventsListener): IDisposable { - this.listeners.push(listener); - return { - dispose: () => { - for (var i = 0; i < this.listeners.length; i++) { - if (this.listeners[i] === listener) { - this.listeners.splice(i, 1); - return; - } - } - } - }; - } - - private addEvent(event: ITimerEvent): void { - event.id = TimeKeeper.EVENT_ID; - TimeKeeper.EVENT_ID++; - this.collectedEvents.push(event); - // expire items from the front of the cache - if (this.collectedEvents.length > TimeKeeper._EVENT_CACHE_LIMIT) { - this.collectedEvents.shift(); - } - } - - private initAutoCleaning(): void { - if (this.cleaningIntervalId === -1) { - this.cleaningIntervalId = Platform.setInterval(() => { - var now = Date.now(); - this.collectedEvents.forEach((event) => { - if (!event.stopTime && (now - event.startTime.getTime()) >= TimeKeeper._MAX_TIMER_LENGTH) { - event.stop(); - } - }); - }, TimeKeeper._CLEAN_UP_INTERVAL); - } - } - - public getCollectedEvents(): ITimerEvent[] { - return this.collectedEvents.slice(0); - } - - public clearCollectedEvents(): void { - this.collectedEvents = []; - } - - _onEventStopped(event: ITimerEvent): void { - var emitEvents = [event]; - - var listeners = this.listeners.slice(0); - for (var i = 0; i < listeners.length; i++) { - try { - listeners[i](emitEvents); - } catch (e) { - errors.onUnexpectedError(e); - } - } - } - - public setInitialCollectedEvents(events: IExistingTimerEvent[], startTime?: Date): void { - if (!this.isEnabled()) { - return; - } - - if (startTime) { - TimeKeeper.PARSE_TIME = startTime; - } - - events.forEach((event) => { - var e = new TimerEvent(this, event.name, event.topic, event.startTime, event.description); - e.stop(event.stopTime); - this.addEvent(e); - }); - } -} - -var timeKeeper = new TimeKeeper(); -export var nullEvent: ITimerEvent = new NullTimerEvent(); - -export function start(topic: Topic | string, name: string, start?: Date, description?: string): ITimerEvent { - return timeKeeper.start(topic, name, start, description); -} - -export function getTimeKeeper(): TimeKeeper { - return timeKeeper; -} diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index 222149de148..aabb762c20e 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -106,7 +106,7 @@ function read(zipPath: string, filePath: string): TPromise { export function buffer(zipPath: string, filePath: string): TPromise { return read(zipPath, filePath).then(stream => { return new TPromise((c, e) => { - const buffers = []; + const buffers: Buffer[] = []; stream.once('error', e); stream.on('data', b => buffers.push(b)); stream.on('end', () => c(Buffer.concat(buffers))); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 18e6deabcbe..4c36e669bd3 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -689,15 +689,15 @@ export class QuickOpenModel implements return this._entries; } - getId(entry: QuickOpenEntry): string { + public getId(entry: QuickOpenEntry): string { return entry.getId(); } - getLabel(entry: QuickOpenEntry): string { + public getLabel(entry: QuickOpenEntry): string { return entry.getLabel(); } - getAriaLabel(entry: QuickOpenEntry): string { + public getAriaLabel(entry: QuickOpenEntry): string { const ariaLabel = entry.getAriaLabel(); if (ariaLabel) { return nls.localize('quickOpenAriaLabelEntry', "{0}, picker", entry.getAriaLabel()); @@ -706,11 +706,11 @@ export class QuickOpenModel implements return nls.localize('quickOpenAriaLabel', "picker"); } - isVisible(entry: QuickOpenEntry): boolean { + public isVisible(entry: QuickOpenEntry): boolean { return !entry.isHidden(); } - run(entry: QuickOpenEntry, mode: Mode, context: IContext): boolean { + public run(entry: QuickOpenEntry, mode: Mode, context: IContext): boolean { return entry.run(mode, context); } } diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts index b59f4b267cc..10029064360 100644 --- a/src/vs/base/parts/tree/browser/tree.ts +++ b/src/vs/base/parts/tree/browser/tree.ts @@ -328,7 +328,7 @@ export interface ITree extends Events.IEventEmitter { * Returns a navigator which allows to discover the visible and * expanded elements in the tree. */ - getNavigator(): INavigator; + getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator; /** * Disposes the tree diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts index 3e8879bdb90..fe1198623fe 100644 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ b/src/vs/base/parts/tree/browser/treeImpl.ts @@ -133,7 +133,7 @@ export class Tree extends Events.EventEmitter implements _.ITree { } public collapse(element: any, recursive: boolean = false): WinJS.Promise { - return this.model.collapse(element); + return this.model.collapse(element, recursive); } public collapseAll(elements: any[] = null, recursive: boolean = false): WinJS.Promise { @@ -318,8 +318,8 @@ export class Tree extends Events.EventEmitter implements _.ITree { return this.model.hasTrait(trait, element); } - getNavigator(): INavigator { - return new MappedNavigator(this.model.getNavigator(), i => i && i.getElement()); + getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator { + return new MappedNavigator(this.model.getNavigator(fromElement, subTreeOnly), i => i && i.getElement()); } public dispose(): void { diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index 21eae022f5b..4da6a3a3b36 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -186,5 +186,8 @@ suite('Filters', () => { filterOk(matchesWords, 'git プル', 'git: プル', [{ start: 0, end: 3 }, { start: 4, end: 7 }]); filterOk(matchesWords, 'öäk', 'Öhm: Älles Klar', [{ start: 0, end: 1 }, { start: 5, end: 6 }, { start: 11, end: 12 }]); + + assert.ok(matchesWords('gipu', 'Category: Git: Pull', true) === null); + assert.deepEqual(matchesWords('pu', 'Category: Git: Pull', true), [{ start: 15, end: 17 }]); }); }); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index f8693d9cd69..f69d864e509 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -13,18 +13,21 @@ import URI from 'vs/base/common/uri'; export class DeferredTPromise extends TPromise { - public canceled = false; + public canceled: boolean; private completeCallback: TValueCallback; private errorCallback: (err: any) => void; private progressCallback: ProgressCallback; constructor() { + let captured: any; super((c, e, p) => { - this.completeCallback = c; - this.errorCallback = e; - this.progressCallback = p; + captured = { c, e, p }; }, () => this.oncancel()); + this.canceled = false; + this.completeCallback = captured.c; + this.errorCallback = captured.e; + this.progressCallback = captured.p; } public complete(value: T) { @@ -51,7 +54,13 @@ export class DeferredPPromise extends PPromise { private progressCallback: TProgressCallback

; constructor(init: (complete: TValueCallback, error: (err: any) => void, progress: TProgressCallback

) => void = (c, e, p) => { }, oncancel?: any) { - super((c, e, p) => { this.completeCallback = c; this.errorCallback = e; this.progressCallback = p; }, oncancel ? oncancel : () => this.oncancel); + let captured: any; + super((c, e, p) => { + captured = { c, e, p }; + }, oncancel ? oncancel : () => this.oncancel); + this.completeCallback = captured.c; + this.errorCallback = captured.e; + this.progressCallback = captured.p; } private oncancel(): void { diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index 554207d24aa..e413e7775bf 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -5,7 +5,7 @@ 'use strict'; -import { IWindowsMainService } from 'vs/code/electron-main/windows'; +import { IWindowsMainService, OpenContext } from 'vs/code/electron-main/windows'; import { VSCodeWindow } from 'vs/code/electron-main/window'; import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -82,6 +82,7 @@ export class LaunchService implements ILaunchService { const openUrlArg = args['open-url'] || []; const openUrl = typeof openUrlArg === 'string' ? [openUrlArg] : openUrlArg; + const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.OTHER; if (openUrl.length > 0) { openUrl.forEach(url => this.urlService.open(url)); @@ -91,17 +92,19 @@ export class LaunchService implements ILaunchService { // Otherwise handle in windows service let usedWindows: VSCodeWindow[]; if (!!args.extensionDevelopmentPath) { - this.windowsService.openExtensionDevelopmentHostWindow({ cli: args, userEnv }); - } else if (args._.length === 0 && args['new-window']) { - usedWindows = this.windowsService.open({ cli: args, userEnv, forceNewWindow: true, forceEmpty: true }); + this.windowsService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv }); + } else if (args._.length === 0 && args['new-window'] || args['new-window-if-not-first']) { + usedWindows = this.windowsService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true }); } else if (args._.length === 0) { - usedWindows = [this.windowsService.focusLastActive(args)]; + usedWindows = [this.windowsService.focusLastActive(args, context)]; } else { usedWindows = this.windowsService.open({ + context, cli: args, userEnv, - forceNewWindow: args.wait || args['new-window'], + forceNewWindow: args.wait || args['new-window'] || args['new-window-if-not-first'], preferNewWindow: !args['reuse-window'], + forceReuseWindow: args['reuse-window'], diffMode: args.diff }); } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 0accc78830e..5eb0da6e884 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -11,7 +11,7 @@ import * as platform from 'vs/base/common/platform'; import { parseMainProcessArgv } from 'vs/platform/environment/node/argv'; import { mkdirp } from 'vs/base/node/pfs'; import { validatePaths } from 'vs/code/electron-main/paths'; -import { IWindowsMainService, WindowsManager } from 'vs/code/electron-main/windows'; +import { IWindowsMainService, WindowsManager, OpenContext } from 'vs/code/electron-main/windows'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc'; import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; @@ -251,12 +251,13 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo windowsMainService.ready(userEnv); // Open our first window + const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.OTHER; if (environmentService.args['new-window'] && environmentService.args._.length === 0) { - windowsMainService.open({ cli: environmentService.args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths + windowsMainService.open({ context, cli: environmentService.args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths } else if (global.macOpenFiles && global.macOpenFiles.length && (!environmentService.args._ || !environmentService.args._.length)) { - windowsMainService.open({ cli: environmentService.args, pathsToOpen: global.macOpenFiles, initialStartup: true }); // mac: open-file event received on startup + windowsMainService.open({ context: OpenContext.DOCK, cli: environmentService.args, pathsToOpen: global.macOpenFiles, initialStartup: true }); // mac: open-file event received on startup } else { - windowsMainService.open({ cli: environmentService.args, forceNewWindow: environmentService.args['new-window'], diffMode: environmentService.args.diff, initialStartup: true }); // default: read paths from cli + windowsMainService.open({ context, cli: environmentService.args, forceNewWindow: environmentService.args['new-window'] || environmentService.args['new-window-if-not-first'], diffMode: environmentService.args.diff, initialStartup: true }); // default: read paths from cli } // Install Menu diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 9a89145c64f..7bfc3bc7ec3 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -10,7 +10,7 @@ import * as platform from 'vs/base/common/platform'; import * as arrays from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem } from 'electron'; -import { IWindowsMainService } from 'vs/code/electron-main/windows'; +import { IWindowsMainService, OpenContext } from 'vs/code/electron-main/windows'; import { VSCodeWindow } from 'vs/code/electron-main/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/code/electron-main/storage'; @@ -315,7 +315,7 @@ export class VSCodeMenu { this.appMenuInstalled = true; const dockMenu = new Menu(); - dockMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsService.openNewWindow() })); + dockMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsService.openNewWindow(OpenContext.DOCK) })); app.dock.setMenu(dockMenu); } @@ -351,19 +351,19 @@ export class VSCodeMenu { let newFile: Electron.MenuItem; if (hasNoWindows) { - newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), click: () => this.windowsService.openNewWindow() })); + newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), click: () => this.windowsService.openNewWindow(OpenContext.MENU) })); } else { newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File"), 'workbench.action.files.newUntitledFile'); } - const open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: () => this.windowsService.openFileFolderPicker() })); - const openFolder = new MenuItem(this.likeAction('workbench.action.files.openFolder', { label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), click: () => this.windowsService.openFolderPicker() })); + const open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: (menuItem, win, event) => this.windowsService.openFileFolderPicker(this.isOptionClick(event)) })); + const openFolder = new MenuItem(this.likeAction('workbench.action.files.openFolder', { label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), click: (menuItem, win, event) => this.windowsService.openFolderPicker(this.isOptionClick(event)) })); let openFile: Electron.MenuItem; if (hasNoWindows) { - openFile = new MenuItem(this.likeAction('workbench.action.files.openFile', { label: mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), click: () => this.windowsService.openFilePicker() })); + openFile = new MenuItem(this.likeAction('workbench.action.files.openFile', { label: mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), click: (menuItem, win, event) => this.windowsService.openFilePicker(this.isOptionClick(event)) })); } else { - openFile = this.createMenuItem(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File..."), 'workbench.action.files.openFile'); + openFile = this.createMenuItem(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File..."), ['workbench.action.files.openFile', 'workbench.action.files.openFileInNewWindow']); } const openRecentMenu = new Menu(); @@ -379,7 +379,7 @@ export class VSCodeMenu { const preferences = this.getPreferencesMenu(); - const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsService.openNewWindow() })); + const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsService.openNewWindow(OpenContext.MENU) })); const revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), 'workbench.action.files.revert', this.windowsService.getWindowCount() > 0); const closeWindow = new MenuItem(this.likeAction('workbench.action.closeWindow', { label: mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window")), click: () => this.windowsService.getLastActiveWindow().win.close(), enabled: this.windowsService.getWindowCount() > 0 })); @@ -468,10 +468,15 @@ export class VSCodeMenu { } private createOpenRecentMenuItem(path: string, actionId: string): Electron.MenuItem { + let label = path; + if ((platform.isMacintosh || platform.isLinux) && path.indexOf(this.environmentService.userHome) === 0) { + label = `~${path.substr(this.environmentService.userHome.length)}`; + } + return new MenuItem(this.likeAction(actionId, { - label: unMnemonicLabel(path), click: (menuItem, win, event) => { - const openInNewWindow = event && ((!platform.isMacintosh && event.ctrlKey) || (platform.isMacintosh && event.metaKey)); - const success = !!this.windowsService.open({ cli: this.environmentService.args, pathsToOpen: [path], forceNewWindow: openInNewWindow }); + label: unMnemonicLabel(label), click: (menuItem, win, event) => { + const openInNewWindow = this.isOptionClick(event); + const success = !!this.windowsService.open({ context: OpenContext.MENU, cli: this.environmentService.args, pathsToOpen: [path], forceNewWindow: openInNewWindow }); if (!success) { this.windowsService.removeFromRecentPathsList(path); } @@ -479,6 +484,10 @@ export class VSCodeMenu { }, false)); } + private isOptionClick(event: Electron.Event): boolean { + return event && ((!platform.isMacintosh && (event.ctrlKey || event.shiftKey)) || (platform.isMacintosh && (event.metaKey || event.altKey))); + } + private createRoleMenuItem(label: string, actionId: string, role: Electron.MenuItemRole): Electron.MenuItem { const options: Electron.MenuItemOptions = { label: mnemonicLabel(label), @@ -890,11 +899,18 @@ export class VSCodeMenu { } } - private createMenuItem(label: string, actionId: string, enabled?: boolean, checked?: boolean): Electron.MenuItem; + private createMenuItem(label: string, actionId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem; private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem; private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem { const label = mnemonicLabel(arg1); - const click: () => void = (typeof arg2 === 'function') ? arg2 : () => this.windowsService.sendToFocused('vscode:runAction', arg2); + const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem, win, event) => { + let actionId = arg2; + if (Array.isArray(arg2)) { + actionId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking + } + + this.windowsService.sendToFocused('vscode:runAction', actionId); + }; const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsService.getWindowCount() > 0; const checked = typeof arg4 === 'boolean' ? arg4 : false; diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 5289884c526..dd967a6e14c 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -82,6 +82,7 @@ export interface IWindowConfiguration extends ParsedArgs { isInitialStartup?: boolean; perfStartTime?: number; + perfAppReady?: number; perfWindowLoadTime?: number; workspacePath?: string; @@ -205,21 +206,6 @@ export class VSCodeWindow implements IVSCodeWindow { this._win = new BrowserWindow(options); this._id = this._win.id; - // TODO@joao: hook this up to some initialization routine - // this causes a race between setting the headers and doing - // a request that needs them. chances are low - getCommonHTTPHeaders().done(headers => { - if (!this._win) { - return; - } - - const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; - - this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => { - cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) }); - }); - }); - if (isFullscreenOrMaximized) { this.win.maximize(); @@ -238,9 +224,28 @@ export class VSCodeWindow implements IVSCodeWindow { this.setMenuBarVisibility(false); // respect configured menu bar visibility } + // TODO@joao: hook this up to some initialization routine + // this causes a race between setting the headers and doing + // a request that needs them. chances are low + this.setCommonHTTPHeaders(); + this.registerListeners(); } + private setCommonHTTPHeaders(): void { + getCommonHTTPHeaders().done(headers => { + if (!this._win) { + return; + } + + const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; + + this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => { + cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) }); + }); + }); + } + public hasHiddenTitleBarStyle(): boolean { return this.hiddenTitleBarStyle; } @@ -478,11 +483,12 @@ export class VSCodeWindow implements IVSCodeWindow { windowConfiguration.fullscreen = this._win.isFullScreen(); // Set Accessibility Config - windowConfiguration.highContrast = platform.isWindows && systemPreferences.isInvertedColorScheme(); + windowConfiguration.highContrast = platform.isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast); windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled(); // Perf Counters windowConfiguration.perfStartTime = global.perfStartTime; + windowConfiguration.perfAppReady = global.perfAppReady; windowConfiguration.perfWindowLoadTime = Date.now(); // Config (combination of process.argv and window configuration) diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 3962312291f..7c442c543d9 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -34,12 +34,32 @@ enum WindowError { CRASHED } +export enum OpenContext { + + // opening when running from the command line + CLI, + + // macOS only: opening from the dock (also when opening files to a running instance from desktop) + DOCK, + + // opening from the main application window + MENU, + + // opening from a file or folder dialog + DIALOG, + + // any other way of opening + OTHER +} + export interface IOpenConfiguration { + context: OpenContext; cli: ParsedArgs; userEnv?: platform.IProcessEnvironment; pathsToOpen?: string[]; preferNewWindow?: boolean; forceNewWindow?: boolean; + forceReuseWindow?: boolean; forceEmpty?: boolean; windowToUse?: VSCodeWindow; diffMode?: boolean; @@ -96,10 +116,10 @@ export interface IWindowsMainService { openFilePicker(forceNewWindow?: boolean, path?: string, window?: VSCodeWindow): void; openFolderPicker(forceNewWindow?: boolean, window?: VSCodeWindow): void; openAccessibilityOptions(): void; - focusLastActive(cli: ParsedArgs): VSCodeWindow; + focusLastActive(cli: ParsedArgs, context: OpenContext): VSCodeWindow; getLastActiveWindow(): VSCodeWindow; findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow; - openNewWindow(): void; + openNewWindow(context: OpenContext): void; sendToFocused(channel: string, ...args: any[]): void; sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void; getFocusedWindow(): VSCodeWindow; @@ -166,7 +186,7 @@ export class WindowsManager implements IWindowsMainService { // Mac only event: open new window when we get activated if (!hasVisibleWindows) { - this.openNewWindow(); + this.openNewWindow(OpenContext.DOCK); } }); @@ -187,7 +207,12 @@ export class WindowsManager implements IWindowsMainService { // Handle paths delayed in case more are coming! runningTimeout = setTimeout(() => { - this.open({ cli: this.environmentService.args, pathsToOpen: macOpenFiles, preferNewWindow: true /* dropping on the dock prefers to open in a new window */ }); + this.open({ + context: OpenContext.DOCK /* can also be opening from finder while app is running */, + cli: this.environmentService.args, + pathsToOpen: macOpenFiles, + preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */ + }); macOpenFiles = []; runningTimeout = null; }, 100); @@ -272,6 +297,8 @@ export class WindowsManager implements IWindowsMainService { } public open(openConfig: IOpenConfiguration): VSCodeWindow[] { + const windowConfig = this.configurationService.getConfiguration('window'); + let iPathsToOpen: IPath[]; const usedWindows: VSCodeWindow[] = []; @@ -345,22 +372,26 @@ export class WindowsManager implements IWindowsMainService { filesToOpen = candidates; } - let openInNewWindow = openConfig.preferNewWindow || openConfig.forceNewWindow; + // let the user settings override how folders are open in a new window or same window unless we are forced + let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow; + if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && windowConfig && (windowConfig.openFoldersInNewWindow === 'on' || windowConfig.openFoldersInNewWindow === 'off')) { + openFolderInNewWindow = (windowConfig.openFoldersInNewWindow === 'on'); + } // Handle files to open/diff or to create when we dont open a folder if (!foldersToOpen.length && (filesToOpen.length > 0 || filesToCreate.length > 0 || filesToDiff.length > 0)) { - // const the user settings override how files are open in a new window or same window unless we are forced + // let the user settings override how files are open in a new window or same window unless we are forced (not for extension development though) let openFilesInNewWindow: boolean; - if (openConfig.forceNewWindow) { - openFilesInNewWindow = true; + if (openConfig.forceNewWindow || openConfig.forceReuseWindow) { + openFilesInNewWindow = openConfig.forceNewWindow && !openConfig.forceReuseWindow; } else { - openFilesInNewWindow = openConfig.preferNewWindow; - if (openFilesInNewWindow && !openConfig.cli.extensionDevelopmentPath) { // can be overriden via settings (not for PDE though!) - const windowConfig = this.configurationService.getConfiguration('window'); - if (windowConfig && !windowConfig.openFilesInNewWindow) { - openFilesInNewWindow = false; // do not open in new window if user configured this explicitly - } + if (openConfig.context === OpenContext.DOCK) { + openFilesInNewWindow = true; // only on macOS do we allow to open files in a new window if this is triggered via DOCK context + } + + if (!openConfig.cli.extensionDevelopmentPath && windowConfig && (windowConfig.openFilesInNewWindow === 'on' || windowConfig.openFilesInNewWindow === 'off' || windowConfig.openFilesInNewWindow === false /* TODO@Ben migration */)) { + openFilesInNewWindow = (windowConfig.openFilesInNewWindow === 'on'); } } @@ -368,8 +399,9 @@ export class WindowsManager implements IWindowsMainService { const lastActiveWindow = this.getLastActiveWindow(); if (!openFilesInNewWindow && lastActiveWindow) { lastActiveWindow.focus(); + const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after lastActiveWindow.ready().then(readyWindow => { - readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff }); + readyWindow.send('vscode:openFiles', files); }); usedWindows.push(lastActiveWindow); @@ -381,7 +413,7 @@ export class WindowsManager implements IWindowsMainService { const browserWindow = this.openInBrowserWindow(configuration, true /* new window */); usedWindows.push(browserWindow); - openInNewWindow = true; // any other folders to open must open in new window then + openFolderInNewWindow = true; // any other folders to open must open in new window then } // Reset these because we handled them @@ -399,8 +431,9 @@ export class WindowsManager implements IWindowsMainService { if (windowsOnWorkspacePath.length > 0) { const browserWindow = windowsOnWorkspacePath[0]; browserWindow.focus(); // just focus one of them + const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after browserWindow.ready().then(readyWindow => { - readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff }); + readyWindow.send('vscode:openFiles', files); }); usedWindows.push(browserWindow); @@ -410,7 +443,7 @@ export class WindowsManager implements IWindowsMainService { filesToCreate = []; filesToDiff = []; - openInNewWindow = true; // any other folders to open must open in new window then + openFolderInNewWindow = true; // any other folders to open must open in new window then } // Open remaining ones @@ -420,7 +453,7 @@ export class WindowsManager implements IWindowsMainService { } const configuration = this.toConfiguration(openConfig, folderToOpen, filesToOpen, filesToCreate, filesToDiff); - const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); + const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse); usedWindows.push(browserWindow); // Reset these because we handled them @@ -428,7 +461,7 @@ export class WindowsManager implements IWindowsMainService { filesToCreate = []; filesToDiff = []; - openInNewWindow = true; // any other folders to open must open in new window then + openFolderInNewWindow = true; // any other folders to open must open in new window then }); } @@ -439,7 +472,7 @@ export class WindowsManager implements IWindowsMainService { const browserWindow = this.openInBrowserWindow(configuration, true /* new window */, null, emptyWorkspaceBackupFolder); usedWindows.push(browserWindow); - openInNewWindow = true; // any other folders to open must open in new window then + openFolderInNewWindow = true; // any other folders to open must open in new window then }); } @@ -447,10 +480,10 @@ export class WindowsManager implements IWindowsMainService { else if (emptyToOpen.length > 0) { emptyToOpen.forEach(() => { const configuration = this.toConfiguration(openConfig); - const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); + const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse); usedWindows.push(browserWindow); - openInNewWindow = true; // any other folders to open must open in new window then + openFolderInNewWindow = true; // any other folders to open must open in new window then }); } @@ -609,7 +642,7 @@ export class WindowsManager implements IWindowsMainService { } // Open it - this.open({ cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0 }); + this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0 }); } private toConfiguration(config: IOpenConfiguration, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration { @@ -889,7 +922,7 @@ export class WindowsManager implements IWindowsMainService { private doPickAndOpen(options: INativeOpenDialogOptions): void { this.getFileOrFolderPaths(options, (paths: string[]) => { if (paths && paths.length) { - this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow }); + this.open({ context: OpenContext.DIALOG, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow }); } }); } @@ -922,7 +955,7 @@ export class WindowsManager implements IWindowsMainService { }); } - public focusLastActive(cli: ParsedArgs): VSCodeWindow { + public focusLastActive(cli: ParsedArgs, context: OpenContext): VSCodeWindow { const lastActive = this.getLastActiveWindow(); if (lastActive) { lastActive.focus(); @@ -932,7 +965,7 @@ export class WindowsManager implements IWindowsMainService { // No window - open new one this.windowsState.openedFolders = []; // make sure we do not open too much - const res = this.open({ cli: cli }); + const res = this.open({ context, cli }); return res && res[0]; } @@ -994,8 +1027,8 @@ export class WindowsManager implements IWindowsMainService { return null; } - public openNewWindow(): void { - this.open({ cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true }); + public openNewWindow(context: OpenContext): void { + this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true }); } public sendToFocused(channel: string, ...args: any[]): void { diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 26cdc0c5193..34a5c02a2fe 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -41,41 +41,6 @@ class CSSBasedConfigurationCache { } } -class CharWidthReader { - - private _chr: string; - private _width: number; - - public get width(): number { return this._width; } - - constructor(chr: string) { - this._chr = chr; - this._width = 0; - } - - public render(out: HTMLSpanElement): void { - if (this._chr === ' ') { - let htmlString = ' '; - // Repeat character 256 (2^8) times - for (let i = 0; i < 8; i++) { - htmlString += htmlString; - } - out.innerHTML = htmlString; - } else { - let testString = this._chr; - // Repeat character 256 (2^8) times - for (let i = 0; i < 8; i++) { - testString += testString; - } - out.textContent = testString; - } - } - - public read(out: HTMLSpanElement): void { - this._width = out.offsetWidth / 256; - } -} - class CSSBasedConfiguration extends Disposable { public static INSTANCE = new CSSBasedConfiguration(); @@ -154,59 +119,15 @@ class CSSBasedConfiguration extends Disposable { } } - private static _testElementId(index: number): string { - return 'editorSizeProvider' + index; - } - - private static _createTestElements(bareFontInfo: BareFontInfo, readers: CharWidthReader[]): HTMLElement { - let container = document.createElement('div'); - Configuration.applyFontInfoSlow(container, bareFontInfo); - container.style.position = 'absolute'; - container.style.top = '-50000px'; - container.style.width = '50000px'; - - for (let i = 0, len = readers.length; i < len; i++) { - container.appendChild(document.createElement('br')); - - let testElement = document.createElement('span'); - testElement.id = this._testElementId(i); - readers[i].render(testElement); - - container.appendChild(testElement); - } - - container.appendChild(document.createElement('br')); - - return container; - } - - private static _readFromTestElements(readers: CharWidthReader[]): void { - for (let i = 0, len = readers.length; i < len; i++) { - readers[i].read(document.getElementById(this._testElementId(i))); - } - } - - private static _runReaders(bareFontInfo: BareFontInfo, readers: CharWidthReader[]): void { - // Create a test container with all these test elements - let testContainer = this._createTestElements(bareFontInfo, readers); - - // Add the container to the DOM - document.body.appendChild(testContainer); - - // Read various properties - this._readFromTestElements(readers); - - // Remove the container from the DOM - document.body.removeChild(testContainer); - } - private static _actualReadConfiguration(bareFontInfo: BareFontInfo): FontInfo { - let typicalHalfwidthCharacter = new CharWidthReader('n'); - let typicalFullwidthCharacter = new CharWidthReader('\uff4d'); - let space = new CharWidthReader(' '); - let digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].map(chr => new CharWidthReader(chr)); + let canvasElem = document.createElement('canvas'); + let context = canvasElem.getContext('2d'); + context.font = `normal normal normal normal ${bareFontInfo.fontSize}px / ${bareFontInfo.lineHeight}px ${bareFontInfo.fontFamily}`; - this._runReaders(bareFontInfo, digits.concat([typicalHalfwidthCharacter, typicalFullwidthCharacter, space])); + let typicalHalfwidthCharacter = context.measureText('n'); + let typicalFullwidthCharacter = context.measureText('\uff4d'); + let space = context.measureText(' '); + let digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].map(chr => context.measureText(chr)); let maxDigitWidth = 0; for (let i = 0, len = digits.length; i < len; i++) { diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 4084eb9362d..505fe390815 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -7,7 +7,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { EmitterEvent, IEventEmitter } from 'vs/base/common/eventEmitter'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import * as timer from 'vs/base/common/timer'; import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StyleMutator } from 'vs/base/browser/styleMutator'; @@ -899,14 +898,12 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp if (!dom.isInDOM(this.domNode)) { return; } - let t = timer.start(timer.Topic.EDITOR, 'View.render'); let viewPartsToRender = this._getViewPartsToRender(); if (!this.viewLines.shouldRender() && viewPartsToRender.length === 0) { // Nothing to render this.keyboardHandler.writeToTextArea(); - t.stop(); return; } @@ -940,8 +937,6 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp // Render the scrollbar this.layoutProvider.renderScrollbar(); - - t.stop(); } private _setHasFocus(newHasFocus: boolean): void { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 11f9a39f1b1..dfd9904fa86 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -598,50 +598,8 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed protected abstract readConfiguration(styling: editorCommon.BareFontInfo): editorCommon.FontInfo; } -/** - * Helper to update Monaco Editor Settings from configurations service. - */ -export class EditorConfiguration { - public static EDITOR_SECTION = 'editor'; - public static DIFF_EDITOR_SECTION = 'diffEditor'; - - /** - * Ask the provided configuration service to apply its configuration to the provided editor. - */ - public static apply(config: any, editor: editorCommon.IEditor): void { - if (!config) { - return; - } - - // Editor Settings (Code Editor, Diff, Terminal) - if (editor && typeof editor.updateOptions === 'function') { - let type = editor.getEditorType(); - if (type !== editorCommon.EditorType.ICodeEditor && type !== editorCommon.EditorType.IDiffEditor) { - return; - } - - let editorConfig = config[EditorConfiguration.EDITOR_SECTION]; - if (type === editorCommon.EditorType.IDiffEditor) { - let diffEditorConfig = config[EditorConfiguration.DIFF_EDITOR_SECTION]; - if (diffEditorConfig) { - if (!editorConfig) { - editorConfig = diffEditorConfig; - } else { - editorConfig = objects.mixin(editorConfig, diffEditorConfig); - } - } - } - - if (editorConfig) { - delete editorConfig.readOnly; // Prevent someone from making editor readonly - editor.updateOptions(editorConfig); - } - } - } -} - -let configurationRegistry = Registry.as(Extensions.Configuration); -let editorConfiguration: IConfigurationNode = { +const configurationRegistry = Registry.as(Extensions.Configuration); +const editorConfiguration: IConfigurationNode = { 'id': 'editor', 'order': 5, 'type': 'object', diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts index 3ffa3e17768..f5d1c5c4c9c 100644 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ b/src/vs/editor/common/model/textModelWithTokens.ts @@ -8,7 +8,6 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; -import * as timer from 'vs/base/common/timer'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -294,7 +293,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke this._withModelTokensChangedEventBuilder((eventBuilder) => { - var t1 = timer.start(timer.Topic.EDITOR, 'backgroundTokenization'); toLineNumber = Math.min(this._lines.length, toLineNumber); var MAX_ALLOWED_TIME = 20, @@ -342,8 +340,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke if (this._invalidLineStartIndex < this._lines.length) { this._beginBackgroundTokenization(); } - - t1.stop(); }); } diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index ce1c82562ac..24b52884752 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -125,7 +125,7 @@ const enum CharacterClass { } const classifier = (function () { - let result = new CharacterClassifier(CharacterClass.None); + let result = new CharacterClassifier(CharacterClass.None); const FORCE_TERMINATION_CHARACTERS = ' \t<>\'\"、。。、,.:;?!@#$%&*‘“〈《「『【〔([{「」}])〕】』」》〉”’`~…'; for (let i = 0; i < FORCE_TERMINATION_CHARACTERS.length; i++) { diff --git a/src/vs/editor/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index 90c3ae98474..bb877af77af 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -34,7 +34,7 @@ class ChangeRecorder { private _fileService: IFileService; - constructor(fileService: IFileService) { + constructor(fileService?: IFileService) { this._fileService = fileService; } @@ -42,22 +42,25 @@ class ChangeRecorder { const changes: IStringDictionary = Object.create(null); - const stop = this._fileService.onFileChanges((event) => { - event.changes.forEach(change => { + let stop: IDisposable; + if (this._fileService) { + stop = this._fileService.onFileChanges((event) => { + event.changes.forEach(change => { - const key = String(change.resource); - let array = changes[key]; + const key = String(change.resource); + let array = changes[key]; - if (!array) { - changes[key] = array = []; - } + if (!array) { + changes[key] = array = []; + } - array.push(change); + array.push(change); + }); }); - }); + } return { - stop: () => { stop.dispose(); }, + stop: () => { return stop && stop.dispose(); }, hasChanged: (resource: URI) => !!changes[resource.toString()], allChanges: () => flatten(values(changes)) }; @@ -273,14 +276,14 @@ export interface BulkEdit { finish(): TPromise; } -export function bulkEdit(fileService: IFileService, textModelResolverService: ITextModelResolverService, editor: ICommonCodeEditor, edits: IResourceEdit[], progress: IProgressRunner = null): TPromise { - let bulk = createBulkEdit(fileService, textModelResolverService, editor); +export function bulkEdit(textModelResolverService: ITextModelResolverService, editor: ICommonCodeEditor, edits: IResourceEdit[], fileService?: IFileService, progress: IProgressRunner = null): TPromise { + let bulk = createBulkEdit(textModelResolverService, editor, fileService); bulk.add(edits); bulk.progress(progress); return bulk.finish(); } -export function createBulkEdit(fileService: IFileService, textModelResolverService: ITextModelResolverService, editor: ICommonCodeEditor): BulkEdit { +export function createBulkEdit(textModelResolverService: ITextModelResolverService, editor?: ICommonCodeEditor, fileService?: IFileService): BulkEdit { let all: IResourceEdit[] = []; let recording = new ChangeRecorder(fileService).start(); diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index 2c6ebab2d31..326b615d401 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -11,12 +11,11 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { ICommonCodeEditor, IEditorContribution, MouseTargetType, EditorContextKeys, IScrollEvent } from 'vs/editor/common/editorCommon'; import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; @@ -125,9 +124,17 @@ export class ContextMenuController implements IEditorContribution { private _getMenuActions(): IAction[] { const result: IAction[] = []; - const contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService); - fillInActions(contextMenu, this._editor.getModel().uri, result); + + let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService); + const groups = contextMenu.getActions(this._editor.getModel().uri); contextMenu.dispose(); + + for (let group of groups) { + const [, actions] = group; + result.push(...actions); + result.push(new Separator()); + } + result.pop(); // remove last separator return result; } diff --git a/src/vs/editor/contrib/find/browser/findWidget.css b/src/vs/editor/contrib/find/browser/findWidget.css index 38bba6c2f09..91f4f37bd69 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.css +++ b/src/vs/editor/contrib/find/browser/findWidget.css @@ -105,10 +105,12 @@ } .monaco-editor .find-widget.no-results .matchesCount { - background-color: rgba(255,0,0,0.5); + color: #A1260D; } -.monaco-editor.vs-dark .find-widget.no-results .matchesCount { - background-color: rgba(255,0,0,0.3); + +.monaco-editor.vs-dark .find-widget.no-results .matchesCount, +.monaco-editor.hc-black .find-widget.no-results .matchesCount { + color: #F48771 } .monaco-editor .find-widget .matchesCount { diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index bc352658822..e4391583d78 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,7 +43,7 @@ const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replac const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first 999 results are highlighted, but all find operations work on the entire text."); const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}"); -const NLS_NO_RESULTS = nls.localize('label.noResults', "No results"); +const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results"); let MAX_MATCHES_COUNT_WIDTH = 69; const WIDGET_FIXED_WIDTH = 411 - 69; diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index 979421d8f9d..10e0304f3a2 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -76,7 +76,7 @@ export class DefinitionAction extends EditorAction { let result: Location[] = []; for (let i = 0; i < references.length; i++) { let reference = references[i]; - if (!reference) { + if (!reference || !reference.range) { continue; } let {uri, range} = reference; @@ -110,7 +110,7 @@ export class DefinitionAction extends EditorAction { } else { let next = model.nearestReference(editor.getModel().uri, editor.getPosition()); this._openReference(editorService, next, this._configuration.openToSide).then(editor => { - if (model.references.length > 1) { + if (editor && model.references.length > 1) { this._openInPeek(editorService, editor, model); } else { model.dispose(); @@ -128,7 +128,7 @@ export class DefinitionAction extends EditorAction { revealIfVisible: !sideBySide } }, sideBySide).then(editor => { - return editor.getControl(); + return editor && editor.getControl(); }); } diff --git a/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css index 457499a7e36..f94bfa6e862 100644 --- a/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css +++ b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css @@ -8,6 +8,8 @@ display: flex; align-items: center; justify-content: center; + height: 16px; + width: 16px; } .monaco-editor .lightbulb-glyph:hover { @@ -16,12 +18,18 @@ .monaco-editor.vs .lightbulb-glyph { background: url('lightbulb.svg') center center no-repeat; - height: 16px; - width: 16px; } -.monaco-editor.vs-dark .lightbulb-glyph, .monaco-editor.hc-black .lightbulb-glyph { - background: url('lightbulb-dark.svg') center center no-repeat; - height: 16px; - width: 16px; +.monaco-editor.vs .lightbulb-glyph[data-severity="high"]{ + background: url('lightbulb.svg') center center no-repeat; +} + +.monaco-editor.vs-dark .lightbulb-glyph, +.monaco-editor.hc-black .lightbulb-glyph { + background: url('lightbulb-dark.svg') center center no-repeat; +} + +.monaco-editor.vs-dark .lightbulb-glyph[data-severity="high"], +.monaco-editor.hc-black .lightbulb-glyph[data-severity="high"] { + background: url('lightbulb-dark.svg') center center no-repeat; } diff --git a/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.ts b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.ts index 8962a91ff0c..0b46d182719 100644 --- a/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.ts +++ b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.ts @@ -7,9 +7,10 @@ import 'vs/css!./lightBulbWidget'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import Event, { Emitter, any } from 'vs/base/common/event'; +import Severity from 'vs/base/common/severity'; import * as dom from 'vs/base/browser/dom'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; -import { QuickFixComputeEvent } from './quickFixModel'; +import { QuickFixComputeEvent } from 'vs/editor/contrib/quickFix/common/quickFixModel'; export class LightBulbWidget implements IOverlayWidget, IDisposable { @@ -86,7 +87,7 @@ export class LightBulbWidget implements IOverlayWidget, IDisposable { const modelNow = this._model; e.fixes.done(fixes => { if (modelNow === this._model && fixes && fixes.length > 0) { - this.show(e.range.startLineNumber); + this.show(e); } else { this.hide(); } @@ -99,7 +100,8 @@ export class LightBulbWidget implements IOverlayWidget, IDisposable { return this._model; } - show(line: number): void { + show(e: QuickFixComputeEvent): void { + const line = e.range.startLineNumber; if (!this._hasSpaceInGlyphMargin(line)) { return; } @@ -107,6 +109,7 @@ export class LightBulbWidget implements IOverlayWidget, IDisposable { this._line = line; this._visible = true; this._layout(); + this._domNode.dataset['severity'] = e.severity >= Severity.Warning ? 'high' : ''; } } diff --git a/src/vs/editor/contrib/quickFix/browser/quickFix.ts b/src/vs/editor/contrib/quickFix/browser/quickFix.ts index e6ba08288a4..f963b16ee90 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFix.ts +++ b/src/vs/editor/contrib/quickFix/browser/quickFix.ts @@ -16,9 +16,9 @@ import { ICommonCodeEditor, EditorContextKeys, ModeContextKeys, IEditorContribut import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; -import { QuickFixContextMenu } from './quickFixWidget'; -import { LightBulbWidget } from './lightBulbWidget'; -import { QuickFixModel, QuickFixComputeEvent } from './quickFixModel'; +import { QuickFixContextMenu } from 'vs/editor/contrib/quickFix/browser/quickFixWidget'; +import { LightBulbWidget } from 'vs/editor/contrib/quickFix/browser/lightBulbWidget'; +import { QuickFixModel, QuickFixComputeEvent } from 'vs/editor/contrib/quickFix/common/quickFixModel'; @editorContribution export class QuickFixController implements IEditorContribution { @@ -88,7 +88,7 @@ export class QuickFixController implements IEditorContribution { } public triggerFromEditorSelection(): void { - this._model.triggerManual(this._editor.getSelection()); + this._model.triggerManual(); } private _updateLightBulbTitle(): void { diff --git a/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts b/src/vs/editor/contrib/quickFix/common/quickFixModel.ts similarity index 69% rename from src/vs/editor/contrib/quickFix/browser/quickFixModel.ts rename to src/vs/editor/contrib/quickFix/common/quickFixModel.ts index 1890ae127fa..eb680670dff 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts +++ b/src/vs/editor/contrib/quickFix/common/quickFixModel.ts @@ -6,21 +6,21 @@ import * as arrays from 'vs/base/common/arrays'; import Event, { Emitter } from 'vs/base/common/event'; +import Severity from 'vs/base/common/severity'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IMarker, IMarkerService } from 'vs/platform/markers/common/markers'; import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; import { ICommonCodeEditor, IPosition, IRange } from 'vs/editor/common/editorCommon'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeActionProviderRegistry, CodeAction } from 'vs/editor/common/modes'; import { getCodeActions } from '../common/quickFix'; -class QuickFixOracle { +export class QuickFixOracle { private _disposables: IDisposable[] = []; + private _currentRange: IRange; constructor(private _editor: ICommonCodeEditor, private _markerService: IMarkerService, private _signalChange: (e: QuickFixComputeEvent) => any) { @@ -34,29 +34,57 @@ class QuickFixOracle { this._disposables = dispose(this._disposables); } - private _onMarkerChanges(resources: URI[]): void { - const {uri} = this._editor.getModel(); - let affectedBy = false; - for (const resource of resources) { - if (resource.toString() === uri.toString()) { - affectedBy = true; - break; - } + trigger(): void { + let {range, severity} = this._rangeAtPosition(); + if (!range) { + range = this._editor.getSelection(); } - if (affectedBy) { - this._onCursorChange(); - } - } - - private _onCursorChange(): void { - const range = this._markerAtPosition() || this._wordAtPosition(); - this._signalChange({ - type: 'auto', + type: 'manual', + severity, range, position: this._editor.getPosition(), fixes: range && getCodeActions(this._editor.getModel(), this._editor.getModel().validateRange(range)) }); + + } + + private _onMarkerChanges(resources: URI[]): void { + const {uri} = this._editor.getModel(); + for (const resource of resources) { + if (resource.toString() === uri.toString()) { + this._onCursorChange(); + return; + } + } + } + + private _onCursorChange(): void { + const {range, severity} = this._rangeAtPosition(); + if (!Range.equalsRange(this._currentRange, range)) { + this._currentRange = range; + this._signalChange({ + type: 'auto', + severity, + range, + position: this._editor.getPosition(), + fixes: range && getCodeActions(this._editor.getModel(), this._editor.getModel().validateRange(range)) + }); + } + } + + private _rangeAtPosition(): { range: IRange, severity: Severity; } { + let range: IRange; + let severity: Severity; + const marker = this._markerAtPosition(); + if (marker) { + range = Range.lift(marker); + severity = marker.severity; + } else { + range = this._wordAtPosition(); + severity = Severity.Info; + } + return { range, severity }; } private _markerAtPosition(): IMarker { @@ -76,26 +104,23 @@ class QuickFixOracle { } private _wordAtPosition(): IRange { - return; - // todo@joh - enable once we decide to eagerly show the - // light bulb as the cursor moves - // const {positionLineNumber, positionColumn} = this._editor.getSelection(); - // const model = this._editor.getModel(); - - // const info = model.getWordAtPosition({ lineNumber: positionLineNumber, column: positionColumn }); - // if (info) { - // return { - // startLineNumber: positionLineNumber, - // startColumn: info.startColumn, - // endLineNumber: positionLineNumber, - // endColumn: info.endColumn - // }; - // } + const {positionLineNumber, positionColumn} = this._editor.getSelection(); + const model = this._editor.getModel(); + const info = model.getWordAtPosition({ lineNumber: positionLineNumber, column: positionColumn }); + if (info) { + return { + startLineNumber: positionLineNumber, + startColumn: info.startColumn, + endLineNumber: positionLineNumber, + endColumn: info.endColumn + }; + } } } export interface QuickFixComputeEvent { type: 'auto' | 'manual'; + severity: Severity; range: IRange; position: IPosition; fixes: TPromise; @@ -109,7 +134,7 @@ export class QuickFixModel { private _onDidChangeFixes = new Emitter(); private _disposables: IDisposable[] = []; - constructor(editor: ICodeEditor, markerService: IMarkerService) { + constructor(editor: ICommonCodeEditor, markerService: IMarkerService) { this._editor = editor; this._markerService = markerService; @@ -132,7 +157,8 @@ export class QuickFixModel { private _update(): void { if (this._quickFixOracle) { - dispose(this._quickFixOracle); + this._quickFixOracle.dispose(); + this._quickFixOracle = undefined; this._onDidChangeFixes.fire(undefined); } @@ -144,16 +170,9 @@ export class QuickFixModel { } } - triggerManual(selection: Selection): void { - const model = this._editor.getModel(); - if (model) { - const fixes = getCodeActions(model, selection); - this._onDidChangeFixes.fire({ - type: 'manual', - range: selection, - position: { lineNumber: selection.positionLineNumber, column: selection.positionColumn }, - fixes - }); + triggerManual(): void { + if (this._quickFixOracle) { + this._quickFixOracle.trigger(); } } } diff --git a/src/vs/editor/contrib/quickFix/test/common/quickFixModel.test.ts b/src/vs/editor/contrib/quickFix/test/common/quickFixModel.test.ts new file mode 100644 index 00000000000..570ab73830c --- /dev/null +++ b/src/vs/editor/contrib/quickFix/test/common/quickFixModel.test.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Model } from 'vs/editor/common/model/model'; +import { mockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor'; +import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { QuickFixOracle } from 'vs/editor/contrib/quickFix/common/quickFixModel'; +import { CodeActionProviderRegistry } from 'vs/editor/common/modes'; + + +suite('QuickFix', () => { + + let uri = URI.parse('fake:path'); + let model = Model.createFromString('foobar foo bar\nfarboo far boo', undefined, 'foo-lang', uri); + let markerService: MarkerService; + let editor: ICommonCodeEditor; + + let reg = CodeActionProviderRegistry.register('foo-lang', { + provideCodeActions() { + return [{ command: { id: 'test-command', title: 'test', arguments: [] }, score: 1 }]; + } + }); + + setup(() => { + markerService = new MarkerService(); + editor = mockCodeEditor([], { model }); + editor.setPosition({ lineNumber: 1, column: 1 }); + }); + + suiteTeardown(() => { + reg.dispose(); + model.dispose(); + }); + + test('Orcale -> marker added', done => { + + const oracle = new QuickFixOracle(editor, markerService, e => { + assert.equal(e.type, 'auto'); + assert.ok(e.fixes); + + e.fixes.then(fixes => { + oracle.dispose(); + assert.equal(fixes.length, 1); + done(); + }, done); + }); + + // start here + markerService.changeOne('fake', uri, [{ + startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6, + message: 'error', + severity: 1, + code: '', + source: '' + }]); + + }); + + test('Orcale -> position changed', done => { + + markerService.changeOne('fake', uri, [{ + startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6, + message: 'error', + severity: 1, + code: '', + source: '' + }]); + + editor.setPosition({ lineNumber: 2, column: 1 }); + + const oracle = new QuickFixOracle(editor, markerService, e => { + assert.equal(e.type, 'auto'); + assert.ok(e.fixes); + + e.fixes.then(fixes => { + oracle.dispose(); + assert.equal(fixes.length, 1); + done(); + }, done); + }); + + // start here + editor.setPosition({ lineNumber: 1, column: 1 }); + + }); + + test('Oracle -> ask once per marker/word', () => { + let counter = 0; + let reg = CodeActionProviderRegistry.register('foo-lang', { + provideCodeActions() { + counter += 1; + return []; + } + }); + + markerService.changeOne('fake', uri, [{ + startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6, + message: 'error', + severity: 1, + code: '', + source: '' + }]); + + let fixes: TPromise[] = []; + let oracle = new QuickFixOracle(editor, markerService, e => { + fixes.push(e.fixes); + }); + + editor.setPosition({ lineNumber: 1, column: 3 }); // marker + editor.setPosition({ lineNumber: 1, column: 6 }); // (same) marker + editor.setPosition({ lineNumber: 1, column: 8 }); // whitespace + editor.setPosition({ lineNumber: 2, column: 2 }); // word + editor.setPosition({ lineNumber: 2, column: 6 }); // (same) word + + return TPromise.join(fixes).then(_ => { + reg.dispose(); + oracle.dispose(); + assert.equal(counter, 2); + }); + }); + +}); diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 241bf5bd349..249486f2b8f 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -22,6 +22,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { rename } from '../common/rename'; import RenameInputField from './renameInputField'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; +import { optional } from 'vs/platform/instantiation/common/instantiation'; // --- register actions and commands @@ -42,10 +43,10 @@ class RenameController implements IEditorContribution { constructor( private editor: ICodeEditor, @IMessageService private _messageService: IMessageService, - @IFileService private _fileService: IFileService, @ITextModelResolverService private _textModelResolverService: ITextModelResolverService, @IProgressService private _progressService: IProgressService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @optional(IFileService) private _fileService: IFileService ) { this._renameInputField = new RenameInputField(editor); this._renameInputVisible = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); @@ -132,7 +133,7 @@ class RenameController implements IEditorContribution { // start recording of file changes so that we can figure out if a file that // is to be renamed conflicts with another (concurrent) modification - let edit = createBulkEdit(this._fileService, this._textModelResolverService, this.editor); + let edit = createBulkEdit(this._textModelResolverService, this.editor, this._fileService); return rename(this.editor.getModel(), this.editor.getPosition(), newName).then(result => { if (result.rejectReason) { diff --git a/src/vs/editor/contrib/snippet/common/snippet.ts b/src/vs/editor/contrib/snippet/common/snippet.ts index 0c9f9766732..ed42ea1dacf 100644 --- a/src/vs/editor/contrib/snippet/common/snippet.ts +++ b/src/vs/editor/contrib/snippet/common/snippet.ts @@ -122,15 +122,15 @@ export class CodeSnippet implements ICodeSnippet { for (let {startLineNumber, startColumn, endLineNumber, endColumn} of originalPlaceHolder.occurences) { - if (startColumn > 1) { - // placeholders that aren't at the beginning of the snippet line + if (startColumn > 1 || startLineNumber === 1) { + // placeholders that aren't at the beginning of new snippet lines // will be moved by how many characters the indentation has been // adjusted startColumn = startColumn + deltaColumns[startLineNumber]; endColumn = endColumn + deltaColumns[endLineNumber]; } else { - // placeholders at the beginning of the snippet line + // placeholders at the beginning of new snippet lines // will be indented by the reference indentation startColumn += referenceIndentation.length; endColumn += referenceIndentation.length; @@ -140,7 +140,7 @@ export class CodeSnippet implements ICodeSnippet { startLineNumber: startLineNumber + deltaLine, startColumn, endLineNumber: endLineNumber + deltaLine, - endColumn + endColumn, }); } diff --git a/src/vs/editor/contrib/snippet/test/common/snippet.test.ts b/src/vs/editor/contrib/snippet/test/common/snippet.test.ts index 63a302d66f3..256d778fdaa 100644 --- a/src/vs/editor/contrib/snippet/test/common/snippet.test.ts +++ b/src/vs/editor/contrib/snippet/test/common/snippet.test.ts @@ -225,7 +225,7 @@ suite('Editor Contrib - Snippets', () => { }); - test('issue #11890: Bad cursor position', () => { + test('issue #11890: Bad cursor position 1/2', () => { let snippet = CodeSnippet.fromTextmate([ 'afterEach((done) => {', @@ -242,6 +242,7 @@ suite('Editor Contrib - Snippets', () => { assert.equal(boundSnippet.lines[1], ' test'); assert.equal(boundSnippet.placeHolders.length, 3); assert.equal(boundSnippet.finishPlaceHolderIndex, 2); + let [first, second] = boundSnippet.placeHolders; assert.equal(first.occurences.length, 1); assert.equal(first.occurences[0].startColumn, 1); @@ -249,6 +250,49 @@ suite('Editor Contrib - Snippets', () => { assert.equal(second.occurences[0].startColumn, 7); }); + test('issue #11890: Bad cursor position 2/2', () => { + + let snippet = CodeSnippet.fromTextmate('${1}\ttest'); + + let boundSnippet = snippet.bind('abc abc abc prefix3', 0, 12, { + normalizeIndentation(str: string): string { + return str.replace(/\t/g, ' '); + } + }); + + assert.equal(boundSnippet.lines[0], '\ttest'); + assert.equal(boundSnippet.placeHolders.length, 2); + assert.equal(boundSnippet.finishPlaceHolderIndex, 1); + + let [first, second] = boundSnippet.placeHolders; + assert.equal(first.occurences.length, 1); + assert.equal(first.occurences[0].startColumn, 13); + assert.equal(second.occurences.length, 1); + assert.equal(second.occurences[0].startColumn, 18); + }); + + test('issue #17989: Bad selection', () => { + + let snippet = CodeSnippet.fromTextmate('${1:HoldMeTight}'); + + let boundSnippet = snippet.bind('abc abc abc prefix3', 0, 12, { + normalizeIndentation(str: string): string { + return str.replace(/\t/g, ' '); + } + }); + + assert.equal(boundSnippet.lines[0], 'HoldMeTight'); + assert.equal(boundSnippet.placeHolders.length, 2); + assert.equal(boundSnippet.finishPlaceHolderIndex, 1); + let [first, second] = boundSnippet.placeHolders; + assert.equal(first.occurences.length, 1); + assert.equal(first.occurences[0].startColumn, 13); + + assert.equal(second.occurences.length, 1); + assert.equal(second.occurences[0].startColumn, 24); + + }); + test('variables, simple', () => { const resolver: ISnippetVariableResolver = { diff --git a/src/vs/editor/contrib/suggest/common/suggestModel.ts b/src/vs/editor/contrib/suggest/common/suggestModel.ts index fc522397f66..7e8eab227da 100644 --- a/src/vs/editor/contrib/suggest/common/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/common/suggestModel.ts @@ -9,9 +9,8 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { forEach } from 'vs/base/common/collections'; import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { startsWith } from 'vs/base/common/strings'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ICommonCodeEditor, ICursorSelectionChangedEvent, CursorChangeReason, IModel, IPosition } from 'vs/editor/common/editorCommon'; +import { ICommonCodeEditor, ICursorSelectionChangedEvent, CursorChangeReason, IModel, IPosition, IWordAtPosition } from 'vs/editor/common/editorCommon'; import { ISuggestSupport, SuggestRegistry } from 'vs/editor/common/modes'; import { provideSuggestionItems, getSuggestionComparator, ISuggestionItem } from './suggest'; import { CompletionModel } from './completionModel'; @@ -30,104 +29,51 @@ export interface ISuggestEvent { auto: boolean; } -export class Context { +export class LineContext { + + static shouldAutoTrigger(editor: ICommonCodeEditor): boolean { + const model = editor.getModel(); + if (!model) { + return false; + } + const pos = editor.getPosition(); + const word = model.getWordAtPosition(pos); + if (!word) { + return false; + } + if (word.endColumn !== pos.column) { + return false; + } + if (!isNaN(Number(word.word))) { + return false; + } + return true; + } + + static isInEditableRange(editor: ICommonCodeEditor): boolean { + const model = editor.getModel(); + const position = editor.getPosition(); + if (model.hasEditableRange()) { + const editableRange = model.getEditableRange(); + if (!editableRange.containsPosition(position)) { + return false; + } + } + return true; + } readonly lineNumber: number; readonly column: number; - readonly isInEditableRange: boolean; - - readonly lineContentBefore: string; - - readonly wordBefore: string; - readonly wordAfter: string; - - constructor(model: IModel, position: IPosition, private auto: boolean) { - const lineContent = model.getLineContent(position.lineNumber); - const wordUnderCursor = model.getWordAtPosition(position); - - if (wordUnderCursor) { - this.wordBefore = lineContent.substring(wordUnderCursor.startColumn - 1, position.column - 1); - this.wordAfter = lineContent.substring(position.column - 1, wordUnderCursor.endColumn - 1); - } else { - this.wordBefore = ''; - this.wordAfter = ''; - } + readonly leadingLineContent: string; + readonly leadingWord: IWordAtPosition; + readonly auto; + constructor(model: IModel, position: IPosition, auto: boolean) { + this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1); + this.leadingWord = model.getWordUntilPosition(position); this.lineNumber = position.lineNumber; this.column = position.column; - this.lineContentBefore = lineContent.substr(0, position.column - 1); - - this.isInEditableRange = true; - if (model.hasEditableRange()) { - const editableRange = model.getEditableRange(); - - if (!editableRange.containsPosition(position)) { - this.isInEditableRange = false; - } - } - } - - shouldAutoTrigger(): boolean { - - if (this.wordBefore.length === 0) { - // Word before position is empty - return false; - } - - if (!isNaN(Number(this.wordBefore))) { - // Word before is number only - return false; - } - - if (this.wordAfter.length > 0) { - // Word after position is non empty - return false; - } - - return true; - } - - isDifferentContext(context: Context): boolean { - if (this.lineNumber !== context.lineNumber) { - // Line number has changed - return true; - } - - if (context.column < this.column - this.wordBefore.length) { - // column went before word start - return true; - } - - if (!startsWith(context.lineContentBefore, this.lineContentBefore)) { - // Line has changed before position - return true; - } - - if (context.wordBefore === '' && context.lineContentBefore !== this.lineContentBefore) { - // Most likely a space has been typed - return true; - } - - return false; - } - - shouldRetrigger(context: Context): boolean { - if (!startsWith(this.lineContentBefore, context.lineContentBefore)) { - // Doesn't look like the same line - return false; - } - - if (this.lineContentBefore.length > context.lineContentBefore.length && this.wordBefore.length === 0) { - // Text was deleted and previous current word was empty - return false; - } - - if (this.auto && context.wordBefore.length === 0) { - // Currently in auto mode and new current word is empty - return false; - } - - return true; + this.auto = auto; } } @@ -147,7 +93,7 @@ export class SuggestModel implements IDisposable { private state: State; private requestPromise: TPromise; - private context: Context; + private context: LineContext; private completionModel: CompletionModel; @@ -274,12 +220,11 @@ export class SuggestModel implements IDisposable { } private onCursorChange(e: ICursorSelectionChangedEvent): void { - if (!e.selection.isEmpty()) { - this.cancel(); - return; - } - if (e.source !== 'keyboard' || e.reason !== CursorChangeReason.NotSet) { + if (!e.selection.isEmpty() + || e.source !== 'keyboard' + || e.reason !== CursorChangeReason.NotSet) { + this.cancel(); return; } @@ -288,32 +233,28 @@ export class SuggestModel implements IDisposable { return; } - const isInactive = this.state === State.Idle; - - if (isInactive && !this.editor.getConfiguration().contribInfo.quickSuggestions) { - return; - } - const model = this.editor.getModel(); - if (!model) { return; } - const ctx = new Context(model, this.editor.getPosition(), false); + if (this.state === State.Idle) { - if (isInactive) { - // trigger was not called or it was canceled - this.cancel(); - - if (ctx.shouldAutoTrigger()) { - this.triggerAutoSuggestPromise = TPromise.timeout(this.quickSuggestDelay); - this.triggerAutoSuggestPromise.then(() => { - this.triggerAutoSuggestPromise = null; - this.trigger(true); - }); + if (this.editor.getConfiguration().contribInfo.quickSuggestions) { + // trigger 24x7 IntelliSense when idle and enabled + this.cancel(); + if (LineContext.shouldAutoTrigger(this.editor)) { + this.triggerAutoSuggestPromise = TPromise.timeout(this.quickSuggestDelay); + this.triggerAutoSuggestPromise.then(() => { + this.triggerAutoSuggestPromise = null; + this.trigger(true); + }); + } } + } else { + // refine active suggestion + const ctx = new LineContext(model, this.editor.getPosition(), this.state === State.Auto); this.onNewContext(ctx); } } @@ -326,9 +267,9 @@ export class SuggestModel implements IDisposable { return; } - const ctx = new Context(model, this.editor.getPosition(), auto); + const ctx = new LineContext(model, this.editor.getPosition(), auto); - if (!ctx.isInEditableRange) { + if (!LineContext.isInEditableRange(this.editor)) { return; } @@ -359,9 +300,9 @@ export class SuggestModel implements IDisposable { items = items.concat(existingItems).sort(cmpFn); } - const ctx = new Context(model, this.editor.getPosition(), auto); + const ctx = new LineContext(model, this.editor.getPosition(), auto); this.completionModel = new CompletionModel(items, this.context.column, { - leadingLineContent: ctx.lineContentBefore, + leadingLineContent: ctx.leadingLineContent, characterCountDelta: this.context ? ctx.column - this.context.column : 0 }); this.onNewContext(ctx); @@ -369,42 +310,66 @@ export class SuggestModel implements IDisposable { }).then(null, onUnexpectedError); } - private onNewContext(ctx: Context): void { - if (this.context && this.context.isDifferentContext(ctx)) { - if (this.context.shouldRetrigger(ctx)) { - this.trigger(this.state === State.Auto, true); - } else { - this.cancel(); + private onNewContext(ctx: LineContext): void { + + if (!this.context) { + // happens when 24x7 IntelliSense is enabled and still in its delay + return; + } + + if (ctx.lineNumber !== this.context.lineNumber) { + // e.g. happens when pressing Enter while IntelliSense is computed + this.cancel(); + return; + } + + if (ctx.column < this.context.column) { + // typed -> moved cursor LEFT -> retrigger if still on a word + if (ctx.leadingWord.word) { + this.trigger(this.context.auto, true); } + return; + } - } else if (this.completionModel) { + if (!this.completionModel) { + // happens when IntelliSense is not yet computed + return; + } - if (this.completionModel.incomplete && ctx.column > this.context.column) { - const {complete, incomplete} = this.completionModel.resolveIncompleteInfo(); - this.trigger(this.state === State.Auto, true, incomplete, complete); - return; - } + if (ctx.column > this.context.column && this.completionModel.incomplete) { + // typed -> moved cursor RIGHT & incomple model -> retrigger + const {complete, incomplete} = this.completionModel.resolveIncompleteInfo(); + this.trigger(this.state === State.Auto, true, incomplete, complete); - const auto = this.state === State.Auto; - const oldLineContext = this.completionModel.lineContext; + } else { + // typed -> moved cursor RIGHT -> update UI + let oldLineContext = this.completionModel.lineContext; let isFrozen = false; this.completionModel.lineContext = { - leadingLineContent: ctx.lineContentBefore, - characterCountDelta: this.context ? ctx.column - this.context.column : 0 + leadingLineContent: ctx.leadingLineContent, + characterCountDelta: ctx.column - this.context.column }; - // when explicitly request when the next context goes - // from 'results' to 'no results' freeze - if (!auto && this.completionModel.items.length === 0) { - this.completionModel.lineContext = oldLineContext; - isFrozen = this.completionModel.items.length > 0; + if (this.completionModel.items.length === 0) { + + if (LineContext.shouldAutoTrigger(this.editor) && this.context.leadingWord.endColumn < ctx.leadingWord.startColumn) { + // retrigger when heading into a new word + this.trigger(this.context.auto, true); + return; + } + + if (!this.context.auto) { + // freeze when IntelliSense was manually requested + this.completionModel.lineContext = oldLineContext; + isFrozen = this.completionModel.items.length > 0; + } } this._onDidSuggest.fire({ completionModel: this.completionModel, + auto: this.context.auto, isFrozen, - auto }); } } diff --git a/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts index aa331b329d3..7304fc44d36 100644 --- a/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts @@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Model } from 'vs/editor/common/model/model'; import { ICommonCodeEditor, Handler } from 'vs/editor/common/editorCommon'; import { ISuggestSupport, ISuggestResult, SuggestRegistry } from 'vs/editor/common/modes'; -import { SuggestModel, Context } from 'vs/editor/contrib/suggest/common/suggestModel'; +import { SuggestModel, LineContext } from 'vs/editor/contrib/suggest/common/suggestModel'; import { MockCodeEditor, MockScopeLocation } from 'vs/editor/test/common/mocks/mockCodeEditor'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -49,8 +49,10 @@ suite('SuggestModel - Context', function () { function assertAutoTrigger(offset: number, expected: boolean): void { const pos = model.getPositionAt(offset); - const ctx = new Context(model, pos, false); - assert.equal(ctx.shouldAutoTrigger(), expected); + const editor = createMockEditor(model); + editor.setPosition(pos); + assert.equal(LineContext.shouldAutoTrigger(editor), expected); + editor.dispose(); } assertAutoTrigger(3, true); // end of word, Das| @@ -59,28 +61,6 @@ suite('SuggestModel - Context', function () { assertAutoTrigger(55, false); // number, 1861| }); - test('Context - isDifferentContext', function () { - - // different line - const ctx = new Context(model, { lineNumber: 1, column: 8 }, true); // Das Pfer|d - assert.equal(ctx.isDifferentContext(new Context(model, { lineNumber: 2, column: 1 }, true)), true); - - - function createEndContext(value: string) { - const model = Model.createFromString(value); - const ctx = new Context(model, model.getPositionAt(value.length), true); // Das Pfer|d - return ctx; - } - - // got shorter -> redo - assert.equal(createEndContext('One Two').isDifferentContext(createEndContext('One Tw')), true); - - // got longer inside word -> keep - assert.equal(createEndContext('One Tw').isDifferentContext(createEndContext('One Two')), false); - - // got longer new word -> redo - assert.equal(createEndContext('One Two').isDifferentContext(createEndContext('One Two ')), true); - }); }); suite('SuggestModel - TriggerAndCancelOracle', function () { @@ -239,4 +219,49 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { }); }); }); -}); \ No newline at end of file + + test('#17400: Keep filtering suggestModel.ts after space', function () { + + disposables.push(SuggestRegistry.register({ scheme: 'test' }, { + triggerCharacters: [], + provideCompletionItems(doc, pos) { + return { + currentWord: '', + incomplete: false, + suggestions: [{ + label: 'My Table', + type: 'property', + insertText: 'My Table' + }] + }; + } + })); + + model.setValue(''); + + return withOracle((model, editor) => { + + return assertEvent(model.onDidSuggest, () => { + editor.setPosition({ lineNumber: 1, column: 1 }); + editor.trigger('keyboard', Handler.Type, { text: 'My' }); + + }, event => { + assert.equal(event.auto, true); + assert.equal(event.completionModel.items.length, 1); + const [first] = event.completionModel.items; + assert.equal(first.suggestion.label, 'My Table'); + + return assertEvent(model.onDidSuggest, () => { + editor.setPosition({ lineNumber: 1, column: 3 }); + editor.trigger('keyboard', Handler.Type, { text: ' ' }); + + }, event => { + assert.equal(event.auto, true); + assert.equal(event.completionModel.items.length, 1); + const [first] = event.completionModel.items; + assert.equal(first.suggestion.label, 'My Table'); + }); + }); + }); + }); +}); diff --git a/src/vs/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuItemActionItem.ts index 39d641b2ecb..64152cc193b 100644 --- a/src/vs/platform/actions/browser/menuItemActionItem.ts +++ b/src/vs/platform/actions/browser/menuItemActionItem.ts @@ -17,7 +17,7 @@ import { domEvent } from 'vs/base/browser/event'; import { Emitter } from 'vs/base/common/event'; -export function fillInActions(menu: IMenu, context: any, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, ignoreAltKey?: boolean): void { +export function fillInActions(menu: IMenu, context: any, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }): void { const groups = menu.getActions(context); if (groups.length === 0) { return; @@ -25,11 +25,6 @@ export function fillInActions(menu: IMenu, context: any, target: IAction[] | { p for (let tuple of groups) { let [group, actions] = tuple; - - if (!ignoreAltKey && _altKey.value) { - swapWithAltActionsIfPossible(actions); - } - if (group === 'navigation') { const head = Array.isArray(target) ? target : target.primary; @@ -67,13 +62,6 @@ export function fillInActions(menu: IMenu, context: any, target: IAction[] | { p } } -function swapWithAltActionsIfPossible(actions: MenuItemAction[]): void { - for (let i = 0; i < actions.length; i++) { - if (actions[i].alt) { - actions[i] = actions[i].alt; - } - } -} export function createActionItem(action: IAction, keybindingService: IKeybindingService, messageService: IMessageService): ActionItem { if (action instanceof MenuItemAction) { @@ -86,8 +74,6 @@ const _altKey = new class extends Emitter { private _subscriptions: IDisposable[] = []; - private _value = false; - constructor() { super(); @@ -97,15 +83,6 @@ const _altKey = new class extends Emitter { this._subscriptions.push(domEvent(document.body, 'blur')(e => this.fire(false))); } - fire(value: boolean) { - super.fire(value); - this._value = value; - } - - get value() { - return this._value; - } - dispose() { super.dispose(); this._subscriptions = dispose(this._subscriptions); diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 009e6a78feb..fe9e02d2647 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -82,7 +82,7 @@ export function getConfigurationValue(config: any, settingPath: string, defau let current = config; for (let i = 0; i < path.length; i++) { current = current[path[i]]; - if (typeof current === 'undefined') { + if (typeof current === 'undefined' || current === null) { return undefined; } } diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index d789272604a..ea5a3ee80fe 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -9,6 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Event from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; export const IEditorService = createDecorator('editorService'); @@ -238,6 +239,11 @@ export interface ITextEditorOptions extends IEditorOptions { endColumn?: number; }; + /** + * Text editor view state. + */ + viewState?: IEditorViewState; + /** * Option to scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. */ diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index a95ec8f2bea..36ecf5f1fb6 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -14,6 +14,7 @@ export interface ParsedArgs { diff?: boolean; goto?: boolean; 'new-window'?: boolean; + 'new-window-if-not-first'?: boolean; 'reuse-window'?: boolean; locale?: string; 'user-data-dir'?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 13e55e6f3fb..c6f6cc136c5 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -30,6 +30,7 @@ const options: minimist.Opts = { 'diff', 'goto', 'new-window', + 'new-window-if-not-first', 'reuse-window', 'performance', 'verbose', @@ -148,7 +149,7 @@ export function formatOptions(options: { [name: string]: string; }, columns: num } function wrapText(text: string, columns: number): string[] { - let lines = []; + let lines: string[] = []; while (text.length) { let index = text.length < columns ? text.length : text.lastIndexOf(' ', columns); let line = text.slice(0, index).trim(); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index e3e072b288e..37f5a52df21 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -223,7 +223,7 @@ export class FileChangesEvent extends events.Event { return false; } - return this._changes.some((change) => { + return this._changes.some(change => { if (change.type !== type) { return false; } @@ -280,11 +280,11 @@ export class FileChangesEvent extends events.Event { } private getOfType(type: FileChangeType): IFileChange[] { - return this._changes.filter((change) => change.type === type); + return this._changes.filter(change => change.type === type); } private hasType(type: FileChangeType): boolean { - return this._changes.some((change) => { + return this._changes.some(change => { return change.type === type; }); } diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index f4fb6eb0dfb..27ffed19554 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -123,13 +123,15 @@ export class InstantiationService implements IInstantiationService { // arguments defined by service decorators let serviceDependencies = _util.getServiceDependencies(desc.ctor).sort((a, b) => a.index - b.index); - let serviceArgs = serviceDependencies.map(dependency => { + let serviceArgs: any[] = []; + for (const dependency of serviceDependencies) { let service = this._getOrCreateServiceInstance(dependency.id); if (!service && this._strict && !dependency.optional) { throw new Error(`[createInstance] ${desc.ctor.name} depends on UNKNOWN service ${dependency.id}.`); } - return service; - }); + serviceArgs.push(service); + } + let firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : staticArgs.length; // check for argument mismatches, adjust static args if needed diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 02d64285d52..ea1ef26e4ce 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -23,7 +23,7 @@ export interface ICommandAndKeybindingRule extends IKeybindingRule { } export interface IKeybindingsRegistry { - registerKeybindingRule(rule: IKeybindingRule); + registerKeybindingRule(rule: IKeybindingRule): void; registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): void; getDefaultKeybindings(): IKeybindingItem[]; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index ee7e6f481c5..51aee630f63 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -25,6 +25,7 @@ export interface IWindowsService { openDevTools(windowId: number): TPromise; toggleDevTools(windowId: number): TPromise; // TODO@joao: rename, shouldn't this be closeWindow? + // @ben: no, this actually leaves the window open but changes it to have no workspace opened closeFolder(windowId: number): TPromise; toggleFullScreen(windowId: number): TPromise; setRepresentedFilename(windowId: number, fileName: string): TPromise; @@ -40,8 +41,7 @@ export interface IWindowsService { quit(): TPromise; // Global methods - // TODO@joao: rename, shouldn't this be openWindow? - windowOpen(paths: string[], forceNewWindow?: boolean): TPromise; + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise; openNewWindow(): TPromise; showWindow(windowId: number): TPromise; getWindows(): TPromise<{ id: number; path: string; title: string; }[]>; @@ -87,7 +87,8 @@ export interface IWindowService { } export interface IWindowSettings { - openFilesInNewWindow: boolean; + openFilesInNewWindow: 'on' | 'off' | 'default'; + openFoldersInNewWindow: 'on' | 'off' | 'default'; reopenFolders: 'all' | 'one' | 'none'; restoreFullscreen: boolean; zoomLevel: number; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 42369382626..a02689a28ef 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -31,7 +31,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'setDocumentEdited', arg: [number, boolean]): TPromise; call(command: 'toggleMenuBar', arg: number): TPromise; call(command: 'quit'): TPromise; - call(command: 'windowOpen', arg: [string[], boolean]): TPromise; + call(command: 'openWindow', arg: [string[], { forceNewWindow?: boolean, forceReuseWindow?: boolean }]): TPromise; call(command: 'openNewWindow'): TPromise; call(command: 'showWindow', arg: number): TPromise; call(command: 'getWindows'): TPromise<{ id: number; path: string; title: string; }[]>; @@ -76,7 +76,7 @@ export class WindowsChannel implements IWindowsChannel { case 'unmaximizeWindow': return this.service.unmaximizeWindow(arg); case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); case 'toggleMenuBar': return this.service.toggleMenuBar(arg); - case 'windowOpen': return this.service.windowOpen(arg[0], arg[1]); + case 'openWindow': return this.service.openWindow(arg[0], arg[1]); case 'openNewWindow': return this.service.openNewWindow(); case 'showWindow': return this.service.showWindow(arg); case 'getWindows': return this.service.getWindows(); @@ -179,8 +179,8 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('quit'); } - windowOpen(paths: string[], forceNewWindow?: boolean): TPromise { - return this.channel.call('windowOpen', [paths, forceNewWindow]); + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise { + return this.channel.call('openWindow', [paths, options]); } openNewWindow(): TPromise { diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 8209ff53b5a..7b0e6d47e4b 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -16,7 +16,7 @@ import { fromEventEmitter } from 'vs/base/node/event'; import { IURLService } from 'vs/platform/url/common/url'; // TODO@Joao: remove this dependency, move all implementation to this class -import { IWindowsMainService } from 'vs/code/electron-main/windows'; +import { IWindowsMainService, OpenContext } from 'vs/code/electron-main/windows'; export class WindowsService implements IWindowsService, IDisposable { @@ -94,7 +94,7 @@ export class WindowsService implements IWindowsService, IDisposable { const vscodeWindow = this.windowsMainService.getWindowById(windowId); if (vscodeWindow) { - this.windowsMainService.open({ cli: this.environmentService.args, forceEmpty: true, windowToUse: vscodeWindow }); + this.windowsMainService.open({ context: OpenContext.OTHER, cli: this.environmentService.args, forceEmpty: true, windowToUse: vscodeWindow }); } return TPromise.as(null); @@ -198,17 +198,17 @@ export class WindowsService implements IWindowsService, IDisposable { return TPromise.as(null); } - windowOpen(paths: string[], forceNewWindow?: boolean): TPromise { + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise { if (!paths || !paths.length) { return TPromise.as(null); } - this.windowsMainService.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: forceNewWindow }); + this.windowsMainService.open({ context: OpenContext.OTHER, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options && options.forceNewWindow, forceReuseWindow: options && options.forceReuseWindow }); return TPromise.as(null); } openNewWindow(): TPromise { - this.windowsMainService.openNewWindow(); + this.windowsMainService.openNewWindow(OpenContext.OTHER); return TPromise.as(null); } @@ -271,7 +271,7 @@ export class WindowsService implements IWindowsService, IDisposable { const cli = assign(Object.create(null), this.environmentService.args, { goto: true }); const pathsToOpen = [filePath]; - this.windowsMainService.open({ cli, pathsToOpen }); + this.windowsMainService.open({ context: OpenContext.OTHER, cli, pathsToOpen }); return TPromise.as(null); } diff --git a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts index 0c72506ad9f..f742f730926 100644 --- a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts +++ b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts @@ -579,7 +579,7 @@ export class MainThreadEditorsTracker { } private _findVisibleTextEditorIds(): string[] { - let result = []; + let result: string[] = []; let modelUris = Object.keys(this._model2TextEditors); for (let i = 0, len = modelUris.length; i < len; i++) { let editors = this._model2TextEditors[modelUris[i]]; diff --git a/src/vs/workbench/api/node/mainThreadTreeExplorers.ts b/src/vs/workbench/api/node/mainThreadTreeExplorers.ts index c0f8ded4be4..178c1e4ac93 100644 --- a/src/vs/workbench/api/node/mainThreadTreeExplorers.ts +++ b/src/vs/workbench/api/node/mainThreadTreeExplorers.ts @@ -16,7 +16,7 @@ export class MainThreadTreeExplorers extends MainThreadTreeExplorersShape { private _proxy: ExtHostTreeExplorersShape; constructor( - @IThreadService private threadService: IThreadService, + @IThreadService threadService: IThreadService, @ITreeExplorerService private treeExplorerService: ITreeExplorerService, @IMessageService private messageService: IMessageService, @ICommandService private commandService: ICommandService diff --git a/src/vs/workbench/api/node/mainThreadWorkspace.ts b/src/vs/workbench/api/node/mainThreadWorkspace.ts index cab823119aa..6acf4ad942b 100644 --- a/src/vs/workbench/api/node/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/node/mainThreadWorkspace.ts @@ -30,9 +30,9 @@ export class MainThreadWorkspace extends MainThreadWorkspaceShape { constructor( @ISearchService searchService: ISearchService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @ITextFileService textFileService, - @IWorkbenchEditorService editorService, - @ITextModelResolverService textModelResolverService, + @ITextFileService textFileService: ITextFileService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @ITextModelResolverService textModelResolverService: ITextModelResolverService, @IFileService fileService: IFileService ) { super(); @@ -99,7 +99,7 @@ export class MainThreadWorkspace extends MainThreadWorkspaceShape { } } - return bulkEdit(this._fileService, this._textModelResolverService, codeEditor, edits) + return bulkEdit(this._textModelResolverService, codeEditor, edits, this._fileService) .then(() => true); } } diff --git a/src/vs/workbench/browser/actionBarRegistry.ts b/src/vs/workbench/browser/actionBarRegistry.ts index ef38a2181f7..f54319a684f 100644 --- a/src/vs/workbench/browser/actionBarRegistry.ts +++ b/src/vs/workbench/browser/actionBarRegistry.ts @@ -92,7 +92,7 @@ export class ContributableActionProvider implements IActionProvider { private registry: IActionBarRegistry; constructor() { - this.registry = (Registry.as(Extensions.Actionbar)); + this.registry = Registry.as(Extensions.Actionbar); } private toContext(tree: ITree, element: any): any { diff --git a/src/vs/workbench/browser/actions/configureLocale.ts b/src/vs/workbench/browser/actions/configureLocale.ts index d5b95c1b3f3..452cad675e1 100644 --- a/src/vs/workbench/browser/actions/configureLocale.ts +++ b/src/vs/workbench/browser/actions/configureLocale.ts @@ -89,5 +89,5 @@ const schema: IJSONSchema = } }; -const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); +const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(schemaId, schema); \ No newline at end of file diff --git a/src/vs/workbench/browser/actions/toggleZenMode.ts b/src/vs/workbench/browser/actions/toggleZenMode.ts index 01718c2a6cd..d542e39b8ee 100644 --- a/src/vs/workbench/browser/actions/toggleZenMode.ts +++ b/src/vs/workbench/browser/actions/toggleZenMode.ts @@ -10,7 +10,7 @@ import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry'; -import { IPartService, IZenModeOptions } from 'vs/workbench/services/part/common/partService'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; class ToggleZenMode extends Action { @@ -26,8 +26,8 @@ class ToggleZenMode extends Action { this.enabled = !!this.partService; } - public run(options: IZenModeOptions): TPromise { - this.partService.toggleZenMode(options); + public run(): TPromise { + this.partService.toggleZenMode(); return TPromise.as(null); } } diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 6c2be7ccbf4..e18bb4cd531 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -46,14 +46,10 @@ export abstract class Composite extends WorkbenchComponent implements IComposite return null; } - public get telemetryService(): ITelemetryService { + protected get telemetryService(): ITelemetryService { return this._telemetryService; } - public get telemetryData(): any { - return this._telemetryData; - } - public get onTitleAreaUpdate(): Event { return this._onTitleAreaUpdate.event; } @@ -102,7 +98,7 @@ export abstract class Composite extends WorkbenchComponent implements IComposite // Only submit telemetry data when not running from an integration test if (this._telemetryService && this._telemetryService.publicLog) { - let eventName: string = 'compositeOpen'; + const eventName: string = 'compositeOpen'; this._telemetryService.publicLog(eventName, { composite: this.getId() }); } } @@ -114,7 +110,7 @@ export abstract class Composite extends WorkbenchComponent implements IComposite // Only submit telemetry data when not running from an integration test if (this._telemetryService && this._telemetryService.publicLog) { - let eventName: string = 'compositeShown'; + const eventName: string = 'compositeShown'; this._telemetryData.composite = this.getId(); this._telemetryService.publicLog(eventName, this._telemetryData); } @@ -223,10 +219,10 @@ export abstract class CompositeDescriptor extends AsyncDesc } export abstract class CompositeRegistry { - private composits: CompositeDescriptor[]; + private composites: CompositeDescriptor[]; constructor() { - this.composits = []; + this.composites = []; } protected registerComposite(descriptor: CompositeDescriptor): void { @@ -234,25 +230,25 @@ export abstract class CompositeRegistry { return; } - this.composits.push(descriptor); + this.composites.push(descriptor); } public getComposite(id: string): CompositeDescriptor { return this.compositeById(id); } - protected getComposits(): CompositeDescriptor[] { - return this.composits.slice(0); + protected getComposites(): CompositeDescriptor[] { + return this.composites.slice(0); } - protected setComposits(compositsToSet: CompositeDescriptor[]): void { - this.composits = compositsToSet; + protected setComposites(compositesToSet: CompositeDescriptor[]): void { + this.composites = compositesToSet; } private compositeById(id: string): CompositeDescriptor { - for (let i = 0; i < this.composits.length; i++) { - if (this.composits[i].id === id) { - return this.composits[i]; + for (let i = 0; i < this.composites.length; i++) { + if (this.composites[i].id === id) { + return this.composites[i]; } } diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index dfbd18ee310..ba99b7663df 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -110,7 +110,7 @@ export class ResourceLabel extends IconLabel { const resource = this.label.resource; let title = ''; - if (this.options && this.options.title) { + if (this.options && typeof this.options.title === 'string') { title = this.options.title; } else if (resource) { title = getPathLabel(resource.fsPath); diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index ce15a3dbc54..862686efabe 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -44,7 +44,7 @@ export class PanelRegistry extends CompositeRegistry { * Returns an array of registered panels known to the platform. */ public getPanels(): PanelDescriptor[] { - return this.getComposits(); + return this.getComposites(); } /** @@ -92,13 +92,14 @@ export abstract class TogglePanelAction extends Action { } private isPanelShowing(): boolean { - let panel = this.panelService.getActivePanel(); + const panel = this.panelService.getActivePanel(); + return panel && panel.getId() === this.panelId; } protected isPanelFocussed(): boolean { - let activePanel = this.panelService.getActivePanel(); - let activeElement = document.activeElement; + const activePanel = this.panelService.getActivePanel(); + const activeElement = document.activeElement; return activePanel && activeElement && DOM.isAncestor(activeElement, (activePanel).getContainer().getHTMLElement()); } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index c59bc39484d..417066997c5 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -49,45 +49,31 @@ export abstract class Part extends WorkbenchComponent { /** * Subclasses override to provide a title area implementation. */ - public createTitleArea(parent: Builder): Builder { + protected createTitleArea(parent: Builder): Builder { return null; } - /** - * Returns the title area container. - */ - public getTitleArea(): Builder { - return this.titleArea; - } - /** * Subclasses override to provide a content area implementation. */ - public createContentArea(parent: Builder): Builder { + protected createContentArea(parent: Builder): Builder { return null; } /** * Returns the content area container. */ - public getContentArea(): Builder { + protected getContentArea(): Builder { return this.contentArea; } /** * Subclasses override to provide a status area implementation. */ - public createStatusArea(parent: Builder): Builder { + protected createStatusArea(parent: Builder): Builder { return null; } - /** - * Returns the status area container. - */ - public getStatusArea(): Builder { - return this.statusArea; - } - /** * Layout title, content and status area in the given dimension. */ @@ -138,7 +124,7 @@ export class PartLayout { } public computeStyle(): void { - let containerStyle = this.container.getComputedStyle(); + const containerStyle = this.container.getComputedStyle(); this.containerStyle = { borderLeftWidth: parseInt(containerStyle.getPropertyValue('border-left-width'), 10), borderRightWidth: parseInt(containerStyle.getPropertyValue('border-right-width'), 10), @@ -147,7 +133,7 @@ export class PartLayout { }; if (this.titleArea) { - let titleStyle = this.titleArea.getComputedStyle(); + const titleStyle = this.titleArea.getComputedStyle(); this.titleStyle = { display: titleStyle.getPropertyValue('display'), height: this.titleArea.getTotalSize().height @@ -155,7 +141,7 @@ export class PartLayout { } if (this.statusArea) { - let statusStyle = this.statusArea.getComputedStyle(); + const statusStyle = this.statusArea.getComputedStyle(); this.statusStyle = { display: statusStyle.getPropertyValue('display'), height: this.statusArea.getTotalSize().height @@ -168,11 +154,11 @@ export class PartLayout { this.computeStyle(); } - let width = dimension.width - (this.containerStyle.borderLeftWidth + this.containerStyle.borderRightWidth); - let height = dimension.height - (this.containerStyle.borderTopWidth + this.containerStyle.borderBottomWidth); + const width = dimension.width - (this.containerStyle.borderLeftWidth + this.containerStyle.borderRightWidth); + const height = dimension.height - (this.containerStyle.borderTopWidth + this.containerStyle.borderBottomWidth); // Return the applied sizes to title, content and status - let sizes: Dimension[] = []; + const sizes: Dimension[] = []; // Title Size: Width (Fill), Height (Variable) let titleSize: Dimension; @@ -192,7 +178,7 @@ export class PartLayout { } // Content Size: Width (Fill), Height (Variable) - let contentSize = new Dimension(width, height - titleSize.height - statusSize.height); + const contentSize = new Dimension(width, height - titleSize.height - statusSize.height); sizes.push(titleSize); sizes.push(contentSize); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 13bb3934dd1..044e06f98c2 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -41,7 +41,7 @@ export abstract class CompositePart extends Part { private mapProgressServiceToComposite: { [compositeId: string]: IProgressService; }; private activeComposite: Composite; private lastActiveCompositeId: string; - private instantiatedComposits: Composite[]; + private instantiatedComposites: Composite[]; private titleLabel: Builder; private toolBar: ToolBar; private compositeLoaderPromises: { [compositeId: string]: TPromise; }; @@ -74,7 +74,7 @@ export abstract class CompositePart extends Part { this.mapActionsBindingToComposite = {}; this.mapProgressServiceToComposite = {}; this.activeComposite = null; - this.instantiatedComposits = []; + this.instantiatedComposites = []; this.compositeLoaderPromises = {}; } @@ -152,9 +152,9 @@ export abstract class CompositePart extends Part { protected createComposite(id: string, isActive?: boolean): TPromise { // Check if composite is already created - for (let i = 0; i < this.instantiatedComposits.length; i++) { - if (this.instantiatedComposits[i].getId() === id) { - return TPromise.as(this.instantiatedComposits[i]); + for (let i = 0; i < this.instantiatedComposites.length; i++) { + if (this.instantiatedComposites[i].getId() === id) { + return TPromise.as(this.instantiatedComposites[i]); } } @@ -170,7 +170,7 @@ export abstract class CompositePart extends Part { this.mapProgressServiceToComposite[composite.getId()] = progressService; // Remember as Instantiated - this.instantiatedComposits.push(composite); + this.instantiatedComposites.push(composite); // Register to title area update events from the composite this.instantiatedCompositeListeners.push(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()))); @@ -181,7 +181,7 @@ export abstract class CompositePart extends Part { return composite; }); - // Report progress for slow loading composits + // Report progress for slow loading composites progressService.showWhile(loaderPromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */); // Add to Promise Cache until Loaded @@ -207,7 +207,7 @@ export abstract class CompositePart extends Part { let createCompositePromise: TPromise; - // Composits created for the first time + // Composites created for the first time let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()]; if (!compositeContainer) { @@ -228,7 +228,7 @@ export abstract class CompositePart extends Part { createCompositePromise = TPromise.as(null); } - // Report progress for slow loading composits (but only if we did not create the composits before already) + // Report progress for slow loading composites (but only if we did not create the composites before already) let progressService = this.mapProgressServiceToComposite[composite.getId()]; if (progressService && !compositeContainer) { this.mapProgressServiceToComposite[composite.getId()].showWhile(createCompositePromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */); @@ -351,7 +351,7 @@ export abstract class CompositePart extends Part { secondaryActions.push(...this.getSecondaryActions()); // From Contributions - let actionBarRegistry = Registry.as(Extensions.Actionbar); + let actionBarRegistry = Registry.as(Extensions.Actionbar); primaryActions.push(...actionBarRegistry.getActionBarActionsForContext(this.actionContributionScope, composite)); secondaryActions.push(...actionBarRegistry.getSecondaryActionBarActionsForContext(this.actionContributionScope, composite)); @@ -443,7 +443,7 @@ export abstract class CompositePart extends Part { // Check Registry if (!actionItem) { - let actionBarRegistry = Registry.as(Extensions.Actionbar); + let actionBarRegistry = Registry.as(Extensions.Actionbar); actionItem = actionBarRegistry.getActionItemForContext(this.actionContributionScope, ToolBarContext, action); } @@ -486,7 +486,7 @@ export abstract class CompositePart extends Part { } public shutdown(): void { - this.instantiatedComposits.forEach(i => i.shutdown()); + this.instantiatedComposites.forEach(i => i.shutdown()); super.shutdown(); } @@ -496,11 +496,11 @@ export abstract class CompositePart extends Part { this.mapProgressServiceToComposite = null; this.mapActionsBindingToComposite = null; - for (let i = 0; i < this.instantiatedComposits.length; i++) { - this.instantiatedComposits[i].dispose(); + for (let i = 0; i < this.instantiatedComposites.length; i++) { + this.instantiatedComposites[i].dispose(); } - this.instantiatedComposits = []; + this.instantiatedComposites = []; this.instantiatedCompositeListeners = dispose(this.instantiatedCompositeListeners); diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index aa27a69c0d2..1b584afd8df 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -30,7 +30,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; * This class is only intended to be subclassed and not instantiated. */ export abstract class BaseEditor extends Panel implements IEditor { - private _input: EditorInput; + protected _input: EditorInput; private _options: EditorOptions; private _position: Position; @@ -42,24 +42,10 @@ export abstract class BaseEditor extends Panel implements IEditor { return this._input; } - /** - * Returns the current input of this editor or null if none. - */ - public getInput(): EditorInput { - return this._input || null; - } - public get options(): EditorOptions { return this._options; } - /** - * Returns the current options of this editor or null if none. - */ - public getOptions(): EditorOptions { - return this._options || null; - } - /** * Note: Clients should not call this method, the workbench calls this * method. Calling it otherwise may result in unexpected behavior. diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index ac659ca6480..fef79c08e73 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -56,7 +56,7 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas } public getTitle(): string { - return this.getInput() ? this.getInput().getName() : nls.localize('binaryDiffEditor', "Binary Diff Viewer"); + return this.input ? this.input.getName() : nls.localize('binaryDiffEditor', "Binary Diff Viewer"); } public createEditor(parent: Builder): void { @@ -92,7 +92,7 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas } public setInput(input: EditorInput, options?: EditorOptions): TPromise { - const oldInput = this.getInput(); + const oldInput = this.input; super.setInput(input, options); // Detect options @@ -112,7 +112,7 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas } // Assert that the current input is still the one we expect. This prevents a race condition when loading a diff takes long and another input was set meanwhile - if (!this.getInput() || this.getInput() !== input) { + if (!this.input || this.input !== input) { return null; } diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 4259d775fa2..e28966bcdeb 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -41,7 +41,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { } public getTitle(): string { - return this.getInput() ? this.getInput().getName() : nls.localize('binaryEditor', "Binary Viewer"); + return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer"); } public createEditor(parent: Builder): void { @@ -58,7 +58,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { } public setInput(input: EditorInput, options?: EditorOptions): TPromise { - const oldInput = this.getInput(); + const oldInput = this.input; super.setInput(input, options); // Detect options @@ -78,7 +78,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { } // Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile - if (!this.getInput() || this.getInput() !== input) { + if (!this.input || this.input !== input) { return null; } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 842d7a4f1d4..bccd08207a4 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -14,7 +14,7 @@ import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extens import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput'; -import { StringEditor } from 'vs/workbench/browser/parts/editor/stringEditor'; +import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; @@ -44,10 +44,10 @@ import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommand // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( new EditorDescriptor( - StringEditor.ID, + TextResourceEditor.ID, nls.localize('textEditor', "Text Editor"), - 'vs/workbench/browser/parts/editor/stringEditor', - 'StringEditor' + 'vs/workbench/browser/parts/editor/textResourceEditor', + 'TextResourceEditor' ), [ new SyncDescriptor(StringEditorInput), diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 3ea08428627..fc5ba177a1a 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -8,11 +8,11 @@ import * as types from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands } from 'vs/workbench/common/editor'; +import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible } from 'vs/workbench/common/editor'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditor, Position, POSITIONS } from 'vs/platform/editor/common/editor'; import { EditorContextKeys } from 'vs/editor/common/editorCommon'; -import { TextCompareEditorVisible, TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; +import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { EditorStacksModel } from 'vs/workbench/common/editor/editorStacksModel'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IMessageService, Severity, CloseAction } from 'vs/platform/message/common/message'; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index 7df18064577..ebf4a30612c 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -22,7 +22,6 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { Position, POSITIONS } from 'vs/platform/editor/common/editor'; import { IEditorGroupService, ITabOptions, GroupArrangement, GroupOrientation } from 'vs/workbench/services/group/common/groupService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -139,7 +138,6 @@ export class EditorGroupsControl implements IEditorGroupsControl, IVerticalSashL @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEditorGroupService private editorGroupService: IEditorGroupService, @ITelemetryService private telemetryService: ITelemetryService, - @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private contextKeyService: IContextKeyService, @IExtensionService private extensionService: IExtensionService, @IInstantiationService private instantiationService: IInstantiationService, @@ -760,7 +758,7 @@ export class EditorGroupsControl implements IEditorGroupsControl, IVerticalSashL this.sashTwo.setOrientation(this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL); // Trigger layout - this.arrangeGroups(GroupArrangement.EVEN); + this.arrangeGroups(); } } @@ -768,7 +766,7 @@ export class EditorGroupsControl implements IEditorGroupsControl, IVerticalSashL return this.layoutVertically ? 'vertical' : 'horizontal'; } - public arrangeGroups(arrangement: GroupArrangement): void { + public arrangeGroups(arrangement?: GroupArrangement): void { if (!this.dimension) { return; // too early } @@ -780,27 +778,49 @@ export class EditorGroupsControl implements IEditorGroupsControl, IVerticalSashL return; // need more editors } - // Minimize Others - if (arrangement === GroupArrangement.MINIMIZE_OTHERS) { - POSITIONS.forEach(position => { - if (this.visibleEditors[position]) { - if (position !== this.lastActivePosition) { - this.silosSize[position] = this.minSize; - availableSize -= this.minSize; + switch (arrangement) { + case GroupArrangement.MINIMIZE_OTHERS: + // Minimize Others + POSITIONS.forEach(position => { + if (this.visibleEditors[position]) { + if (position !== this.lastActivePosition) { + this.silosSize[position] = this.minSize; + availableSize -= this.minSize; + } } - } - }); + }); - this.silosSize[this.lastActivePosition] = availableSize; - } + this.silosSize[this.lastActivePosition] = availableSize; + break; + case GroupArrangement.EVEN: + // Even Sizes + POSITIONS.forEach(position => { + if (this.visibleEditors[position]) { + this.silosSize[position] = availableSize / visibleEditors; + } + }); + break; + default: + // Minimized editors should remain minimized, others should keep their relative Sizes + let oldNonMinimizedTotal = 0; + POSITIONS.forEach(position => { + if (this.visibleEditors[position]) { + if (this.silosMinimized[position]) { + this.silosSize[position] = this.minSize; + availableSize -= this.minSize; + } else { + oldNonMinimizedTotal += this.silosSize[position]; + } + } + }); - // Even Sizes - else if (arrangement === GroupArrangement.EVEN) { - POSITIONS.forEach(position => { - if (this.visibleEditors[position]) { - this.silosSize[position] = availableSize / visibleEditors; - } - }); + // Set size for non-minimized editors + const scaleFactor = availableSize / oldNonMinimizedTotal; + POSITIONS.forEach(position => { + if (this.visibleEditors[position] && !this.silosMinimized[position]) { + this.silosSize[position] *= scaleFactor; + } + }); } // Since we triggered a change in minimized/maximized editors, we need diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 923c4a7f2c9..162ea2b23e7 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -21,7 +21,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Scope as MementoScope } from 'vs/workbench/common/memento'; import { Part } from 'vs/workbench/browser/part'; import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorRegistry, Extensions as EditorExtensions, EditorInput, EditorOptions, ConfirmResult, IWorkbenchEditorConfiguration, IEditorDescriptor, TextEditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor'; +import { IEditorRegistry, Extensions as EditorExtensions, EditorInput, EditorOptions, ConfirmResult, IWorkbenchEditorConfiguration, IEditorDescriptor, TextEditorOptions, SideBySideEditorInput, TextCompareEditorVisible, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; import { EditorGroupsControl, Rochade, IEditorGroupsControl, ProgressState } from 'vs/workbench/browser/parts/editor/editorGroupsControl'; import { WorkbenchProgressService } from 'vs/workbench/services/progress/browser/progressService'; import { IEditorGroupService, GroupOrientation, GroupArrangement, ITabOptions } from 'vs/workbench/services/group/common/groupService'; @@ -37,6 +37,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { EditorStacksModel, EditorGroup, EditorIdentifier, GroupEvent } from 'vs/workbench/common/editor/editorStacksModel'; import Event, { Emitter } from 'vs/base/common/event'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; class ProgressMonitor { @@ -93,6 +94,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService private _onGroupOrientationChanged: Emitter; private _onTabOptionsChanged: Emitter; + private textCompareEditorVisible: IContextKey; + // The following data structures are partitioned into array of Position as provided by Services.POSITION array private visibleEditors: BaseEditor[]; private instantiatedEditors: BaseEditor[][]; @@ -109,6 +112,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService @IStorageService private storageService: IStorageService, @IPartService private partService: IPartService, @IConfigurationService private configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private instantiationService: IInstantiationService ) { super(id); @@ -132,6 +136,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService this.stacks = this.instantiationService.createInstance(EditorStacksModel, restoreFromStorage); + this.textCompareEditorVisible = TextCompareEditorVisible.bindTo(contextKeyService); + const config = configurationService.getConfiguration(); if (config && config.workbench && config.workbench.editor) { const editorConfig = config.workbench.editor; @@ -350,6 +356,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Indicate to editor that it is now visible editor.setVisible(true, position); + // Update text compare editor visible context + this.updateTextCompareEditorVisible(); + // Make sure the editor is layed out this.editorGroupsControl.layout(position); @@ -437,7 +446,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService private doSetInput(group: EditorGroup, editor: BaseEditor, input: EditorInput, options: EditorOptions, monitor: ProgressMonitor): TPromise { // Emit Input-Changed Event as appropiate - const previousInput = editor.getInput(); + const previousInput = editor.input; const inputChanged = (!previousInput || !previousInput.matches(input) || (options && options.forceOpen)); // Call into Editor @@ -595,6 +604,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService editor.clearInput(); editor.setVisible(false); + // Update text compare editor visible context + this.updateTextCompareEditorVisible(); + // Clear active editor this.visibleEditors[position] = null; @@ -607,6 +619,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService } } + private updateTextCompareEditorVisible(): void { + this.textCompareEditorVisible.set(this.visibleEditors.some(e => e && e.isVisible() && e.getId() === TEXT_DIFF_EDITOR_ID)); + } + public closeAllEditors(except?: Position): TPromise { let groups = this.stacks.groups.reverse(); // start from the end to prevent layout to happen through rochade diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 7bfbdfae87c..45f8f19f20b 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -46,7 +46,7 @@ export class SideBySideEditor extends BaseEditor { } public setInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise { - const oldInput = this.getInput(); + const oldInput = this.input; return super.setInput(newInput, options) .then(() => this.updateInput(oldInput, newInput, options)); } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index cc3e6831b22..ac4094b5fe1 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -19,7 +19,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { EditorLabel } from 'vs/workbench/browser/labels'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; @@ -59,7 +58,6 @@ export class TabsTitleControl extends TitleControl { constructor( @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @@ -71,7 +69,7 @@ export class TabsTitleControl extends TitleControl { @IQuickOpenService quickOpenService: IQuickOpenService, @IWindowService private windowService: IWindowService ) { - super(contextMenuService, instantiationService, configurationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService); + super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService); this.tabDisposeables = []; this.editorLabels = []; @@ -218,6 +216,11 @@ export class TabsTitleControl extends TitleControl { // Container tabContainer.setAttribute('aria-label', `${name}, tab`); tabContainer.title = verboseDescription; + if (this.tabOptions.showTabCloseButton) { + DOM.removeClass(tabContainer, 'no-close-button'); + } else { + DOM.addClass(tabContainer, 'no-close-button'); + } // Label const tabLabel = this.editorLabels[index]; @@ -362,12 +365,6 @@ export class TabsTitleControl extends TitleControl { tabContainer.setAttribute('role', 'presentation'); // cannot use role "tab" here due to https://github.com/Microsoft/vscode/issues/8659 DOM.addClass(tabContainer, 'tab monaco-editor-background'); - if (!this.tabOptions.showTabCloseButton) { - DOM.addClass(tabContainer, 'no-close-button'); - } else { - DOM.removeClass(tabContainer, 'no-close-button'); - } - // Tab Editor Label const editorLabel = this.instantiationService.createInstance(EditorLabel, tabContainer, void 0); this.editorLabels.push(editorLabel); @@ -425,7 +422,7 @@ export class TabsTitleControl extends TitleControl { } private hookTabListeners(tab: HTMLElement, index: number): IDisposable { - const disposables = []; + const disposables: IDisposable[] = []; // Open on Click disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 4a8496f3006..621985ff420 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -12,7 +12,6 @@ import { Builder } from 'vs/base/browser/builder'; import { Action, IAction } from 'vs/base/common/actions'; import { onUnexpectedError } from 'vs/base/common/errors'; import types = require('vs/base/common/types'); -import { Position } from 'vs/platform/editor/common/editor'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/editorCommon'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; @@ -26,20 +25,15 @@ import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorMo import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IFileOperationResult, FileOperationResult } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IMessageService } from 'vs/platform/message/common/message'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -export const TextCompareEditorVisible = new RawContextKey('textCompareEditorVisible', false); - /** * The text editor that leverages the diff text editor for the editing experience. */ @@ -51,24 +45,17 @@ export class TextDiffEditor extends BaseTextEditor { private nextDiffAction: NavigateAction; private previousDiffAction: NavigateAction; - private textDiffEditorVisible: IContextKey; - constructor( @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @IMessageService messageService: IMessageService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, @IEditorGroupService private editorGroupService: IEditorGroupService, @ITextFileService textFileService: ITextFileService ) { - super(TextDiffEditor.ID, telemetryService, instantiationService, contextService, storageService, messageService, configurationService, editorService, themeService, textFileService); - - this.textDiffEditorVisible = TextCompareEditorVisible.bindTo(contextKeyService); + super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService); } public getTitle(): string { @@ -90,12 +77,12 @@ export class TextDiffEditor extends BaseTextEditor { // Check if arg4 is a position argument that differs from this editors position if (types.isUndefinedOrNull(arg3) || arg3 === false || arg3 === this.position) { - const activeDiffInput = this.getInput(); + const activeDiffInput = this.input; if (input && options && activeDiffInput) { // Input matches modified side of the diff editor: perform the action on modified side if (input.matches(activeDiffInput.modifiedInput)) { - return this.setInput(this.getInput(), options).then(() => this); + return this.setInput(this.input, options).then(() => this); } // Input matches original side of the diff editor: perform the action on original side @@ -120,7 +107,7 @@ export class TextDiffEditor extends BaseTextEditor { } public setInput(input: EditorInput, options?: EditorOptions): TPromise { - const oldInput = this.getInput(); + const oldInput = this.input; super.setInput(input, options); // Detect options @@ -152,7 +139,7 @@ export class TextDiffEditor extends BaseTextEditor { } // Assert that the current input is still the one we expect. This prevents a race condition when loading a diff takes long and another input was set meanwhile - if (!this.getInput() || this.getInput() !== input) { + if (!this.input || this.input !== input) { return null; } @@ -267,12 +254,6 @@ export class TextDiffEditor extends BaseTextEditor { super.clearInput(); } - public setEditorVisible(visible: boolean, position: Position): void { - this.textDiffEditorVisible.set(visible); - - super.setEditorVisible(visible, position); - } - public getDiffNavigator(): DiffNavigator { return this.diffNavigator; } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index ef22fe877b6..18a16159854 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -9,21 +9,17 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Dimension, Builder } from 'vs/base/browser/builder'; import objects = require('vs/base/common/objects'); import errors = require('vs/base/common/errors'); +import types = require('vs/base/common/types'); import DOM = require('vs/base/browser/dom'); import { CodeEditor } from 'vs/editor/browser/codeEditor'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorConfiguration } from 'vs/editor/common/config/commonEditorConfig'; -import { IEditorViewState, IEditor, IEditorOptions, EventType as EditorEventType } from 'vs/editor/common/editorCommon'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IFilesConfiguration } from 'vs/platform/files/common/files'; +import { IEditorViewState, IEditor, IEditorOptions, EventType as EditorEventType, EditorType } from 'vs/editor/common/editorCommon'; import { Position } from 'vs/platform/editor/common/editor'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMessageService } from 'vs/platform/message/common/message'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { EventEmitter } from 'vs/base/common/eventEmitter'; @@ -37,6 +33,11 @@ interface ITextEditorViewState { 2?: IEditorViewState; } +interface IEditorConfiguration { + editor: any; + diffEditor: any; +} + /** * The base class of editors that leverage the text editor for the editing experience. This class is only intended to * be subclassed and not instantiated. @@ -51,45 +52,22 @@ export abstract class BaseTextEditor extends BaseEditor { id: string, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private _instantiationService: IInstantiationService, - @IWorkspaceContextService private _contextService: IWorkspaceContextService, - @IStorageService private _storageService: IStorageService, - @IMessageService private _messageService: IMessageService, + @IStorageService private storageService: IStorageService, @IConfigurationService private configurationService: IConfigurationService, - @IWorkbenchEditorService private _editorService: IWorkbenchEditorService, @IThemeService private themeService: IThemeService, @ITextFileService private textFileService: ITextFileService ) { super(id, telemetryService); this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.handleConfigurationChangeEvent(e.config))); - this.toUnbind.push(themeService.onDidColorThemeChange(_ => this.handleConfigurationChangeEvent())); + this.toUnbind.push(themeService.onDidColorThemeChange(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration()))); } - public get instantiationService(): IInstantiationService { + protected get instantiationService(): IInstantiationService { return this._instantiationService; } - public get contextService(): IWorkspaceContextService { - return this._contextService; - } - - public get storageService(): IStorageService { - return this._storageService; - } - - public get messageService() { - return this._messageService; - } - - public get editorService() { - return this._editorService; - } - - public get editorContainer(): Builder { - return this._editorContainer; - } - - private handleConfigurationChangeEvent(configuration?: any): void { + private handleConfigurationChangeEvent(configuration: IEditorConfiguration): void { if (this.isVisible()) { this.applyConfiguration(configuration); } else { @@ -99,28 +77,27 @@ export abstract class BaseTextEditor extends BaseEditor { private consumePendingConfigurationChangeEvent(): void { if (this.hasPendingConfigurationChange) { - this.applyConfiguration(this.configurationService.getConfiguration()); + this.applyConfiguration(this.configurationService.getConfiguration()); this.hasPendingConfigurationChange = false; } } - protected applyConfiguration(configuration?: any): void { + private applyConfiguration(configuration: IEditorConfiguration): void { if (!this.editorControl) { return; } - // Configuration & Options - if (configuration) { - const specificEditorSettings = this.getCodeEditorOptions(); - configuration = objects.clone(configuration); // dont modify original config - objects.assign(configuration[EditorConfiguration.EDITOR_SECTION], specificEditorSettings); - EditorConfiguration.apply(configuration, this.editorControl); + // Specific editor options always overwrite user configuration + const editorConfiguration = types.isObject(configuration.editor) ? objects.clone(configuration.editor) : Object.create(null); + objects.assign(editorConfiguration, this.getCodeEditorOptions()); + + // Handle diff editor specially by merging in diffEditor configuration + if (this.editorControl.getEditorType() === EditorType.IDiffEditor && types.isObject(configuration.diffEditor)) { + objects.mixin(editorConfiguration, configuration.diffEditor); } - // Just options - else { - this.editorControl.updateOptions(this.getCodeEditorOptions()); - } + // Apply to control + this.editorControl.updateOptions(editorConfiguration); } protected getCodeEditorOptions(): IEditorOptions { @@ -145,7 +122,7 @@ export abstract class BaseTextEditor extends BaseEditor { this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost())); // Configuration - this.applyConfiguration(this.configurationService.getConfiguration()); + this.applyConfiguration(this.configurationService.getConfiguration()); } private onEditorFocusLost(): void { diff --git a/src/vs/workbench/browser/parts/editor/stringEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts similarity index 87% rename from src/vs/workbench/browser/parts/editor/stringEditor.ts rename to src/vs/workbench/browser/parts/editor/textResourceEditor.ts index f68bb284d8b..88871b501c4 100644 --- a/src/vs/workbench/browser/parts/editor/stringEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -15,39 +15,33 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import URI from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMessageService } from 'vs/platform/message/common/message'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; /** - * An editor implementation that is capable of showing string inputs or promise inputs that resolve to a string. - * Uses the TextEditor widget to show the contents. + * An editor implementation that is capable of showing the contents of resource inputs. Uses + * the TextEditor widget to show the contents. */ -export class StringEditor extends BaseTextEditor { +export class TextResourceEditor extends BaseTextEditor { - public static ID = 'workbench.editors.stringEditor'; + public static ID = 'workbench.editors.textResourceEditor'; constructor( @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @IMessageService messageService: IMessageService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IEditorGroupService private editorGroupService: IEditorGroupService, @ITextFileService textFileService: ITextFileService ) { - super(StringEditor.ID, telemetryService, instantiationService, contextService, storageService, messageService, configurationService, editorService, themeService, textFileService); + super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService); this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDirtyChange(e))); } @@ -67,7 +61,7 @@ export class StringEditor extends BaseTextEditor { } public setInput(input: EditorInput, options?: EditorOptions): TPromise { - const oldInput = this.getInput(); + const oldInput = this.input; super.setInput(input, options); // Detect options @@ -97,7 +91,7 @@ export class StringEditor extends BaseTextEditor { } // Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile - if (!this.getInput() || this.getInput() !== input) { + if (!this.input || this.input !== input) { return null; } @@ -135,7 +129,7 @@ export class StringEditor extends BaseTextEditor { protected getCodeEditorOptions(): IEditorOptions { const options = super.getCodeEditorOptions(); - const input = this.getInput(); + const input = this.input; const isUntitled = input instanceof UntitledEditorInput; const isReadonly = !isUntitled; // all string editors are readonly except for the untitled one diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 716e65cb6ac..bd997951c32 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -23,7 +23,6 @@ import { IActionItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/a import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupService, ITabOptions } from 'vs/workbench/services/group/common/groupService'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -94,7 +93,6 @@ export abstract class TitleControl implements ITitleAreaControl { constructor( @IContextMenuService protected contextMenuService: IContextMenuService, @IInstantiationService protected instantiationService: IInstantiationService, - @IConfigurationService protected configurationService: IConfigurationService, @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, @IEditorGroupService protected editorGroupService: IEditorGroupService, @IContextKeyService protected contextKeyService: IContextKeyService, @@ -323,7 +321,7 @@ export abstract class TitleControl implements ITitleAreaControl { const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService); this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => this.update())); - fillInActions(titleBarMenu, this.resourceContext.get(), { primary, secondary }, true); + fillInActions(titleBarMenu, this.resourceContext.get(), { primary, secondary }); } return { primary, secondary }; diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 42eea7c819f..5d0395fd3f5 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -201,8 +201,8 @@ class ToggleMaximizedPanelAction extends Action { } } -let actionRegistry = Registry.as(WorkbenchExtensions.WorkbenchActions); +const actionRegistry = Registry.as(WorkbenchExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelAction, TogglePanelAction.ID, TogglePanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel Visibility', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index c0ff71dd522..e1acf4872ec 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -342,9 +342,9 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe // Model const model = new QuickOpenModel(); - const entries = picks.map(e => this.instantiationService.createInstance(PickOpenEntry, e, () => progress(e))); + const entries = picks.map((e, index) => this.instantiationService.createInstance(PickOpenEntry, e, index, () => progress(e))); if (picks.length === 0) { - entries.push(this.instantiationService.createInstance(PickOpenEntry, { label: nls.localize('emptyPicks', "There are no entries to pick from") }, null)); + entries.push(this.instantiationService.createInstance(PickOpenEntry, { label: nls.localize('emptyPicks', "There are no entries to pick from") }, 0, null)); } model.setEntries(entries); @@ -358,9 +358,9 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe let index = -1; let context: IEntryRunContext; - entries.forEach((entry, i) => { + entries.forEach(entry => { if (entry.shouldRunWithContext) { - index = i; + index = entry.index; context = entry.shouldRunWithContext; } }); @@ -414,6 +414,15 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe }); } + // Sort by value + model.entries.sort((pickA: PickOpenEntry, pickB: PickOpenEntry) => { + if (!value) { + return pickA.index - pickB.index; // restore natural order + } + + return QuickOpenEntry.compare(pickA, pickB, value); + }); + this.pickOpenWidget.refresh(model, value ? { autoFocusFirstEntry: true } : autoFocus); }, onShow: () => this.handleOnShow(true), @@ -1001,6 +1010,7 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry { constructor( item: IPickOpenEntry, + private _index: number, private onPreview: () => void, @IModeService private modeService: IModeService, @IModelService private modelService: IModelService @@ -1018,6 +1028,10 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry { this.isFolder = fileItem.isFolder; } + public get index(): number { + return this._index; + } + public getLabelOptions(): IIconLabelOptions { return { extraClasses: this.resource ? getIconClasses(this.modelService, this.modeService, this.resource, this.isFolder) : [] diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 629f6f925dd..c381a4b44c2 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -141,7 +141,7 @@ class FocusSideBarAction extends Action { } } -let registry = Registry.as(ActionExtensions.WorkbenchActions); +const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_0 }), 'View: Focus into Side Bar', nls.localize('viewCategory', "View")); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbar.ts b/src/vs/workbench/browser/parts/statusbar/statusbar.ts index 431ca9bbbe0..6eb4f458930 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbar.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbar.ts @@ -6,9 +6,7 @@ import { Registry } from 'vs/platform/platform'; import { IDisposable } from 'vs/base/common/lifecycle'; -/* tslint:disable:no-unused-variable */ -import statusbarService = require('vs/platform/statusbar/common/statusbar'); -/* tslint:enable:no-unused-variable */ +import * as statusbarService from 'vs/platform/statusbar/common/statusbar'; import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 3be9c056516..ac76450fd17 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -50,17 +50,17 @@ export class StatusbarPart extends Part implements IStatusbarService { public addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable { // Render entry in status bar - let el = this.doCreateStatusItem(alignment, priority); - let item = this.instantiationService.createInstance(StatusBarEntryItem, entry); - let toDispose = item.render(el); + const el = this.doCreateStatusItem(alignment, priority); + const item = this.instantiationService.createInstance(StatusBarEntryItem, entry); + const toDispose = item.render(el); // Insert according to priority - let container = this.statusItemsContainer.getHTMLElement(); - let neighbours = this.getEntries(alignment); + const container = this.statusItemsContainer.getHTMLElement(); + const neighbours = this.getEntries(alignment); let inserted = false; for (let i = 0; i < neighbours.length; i++) { - let neighbour = neighbours[i]; - let nPriority = $(neighbour).getProperty(StatusbarPart.PRIORITY_PROP); + const neighbour = neighbours[i]; + const nPriority = $(neighbour).getProperty(StatusbarPart.PRIORITY_PROP); if ( alignment === StatusbarAlignment.LEFT && nPriority < priority || alignment === StatusbarAlignment.RIGHT && nPriority > priority @@ -87,12 +87,12 @@ export class StatusbarPart extends Part implements IStatusbarService { } private getEntries(alignment: StatusbarAlignment): HTMLElement[] { - let entries: HTMLElement[] = []; + const entries: HTMLElement[] = []; - let container = this.statusItemsContainer.getHTMLElement(); - let children = container.children; + const container = this.statusItemsContainer.getHTMLElement(); + const children = container.children; for (let i = 0; i < children.length; i++) { - let childElement = children.item(i); + const childElement = children.item(i); if ($(childElement).getProperty(StatusbarPart.ALIGNMENT_PROP) === alignment) { entries.push(childElement); } @@ -105,18 +105,18 @@ export class StatusbarPart extends Part implements IStatusbarService { this.statusItemsContainer = $(parent); // Fill in initial items that were contributed from the registry - let registry = (Registry.as(Extensions.Statusbar)); + const registry = Registry.as(Extensions.Statusbar); - let leftDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.LEFT).sort((a, b) => b.priority - a.priority); - let rightDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.RIGHT).sort((a, b) => a.priority - b.priority); + const leftDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.LEFT).sort((a, b) => b.priority - a.priority); + const rightDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.RIGHT).sort((a, b) => a.priority - b.priority); - let descriptors = rightDescriptors.concat(leftDescriptors); // right first because they float + const descriptors = rightDescriptors.concat(leftDescriptors); // right first because they float this.toDispose.push(...descriptors.map(descriptor => { - let item = this.instantiationService.createInstance(descriptor.syncDescriptor); - let el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority); + const item = this.instantiationService.createInstance(descriptor.syncDescriptor); + const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority); - let dispose = item.render(el); + const dispose = item.render(el); this.statusItemsContainer.append(el); return dispose; @@ -126,7 +126,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0): HTMLElement { - let el = document.createElement('div'); + const el = document.createElement('div'); dom.addClass(el, 'statusbar-item'); if (alignment === StatusbarAlignment.RIGHT) { @@ -258,9 +258,9 @@ class StatusBarEntryItem implements IStatusbarItem { private executeCommand(id: string) { // Lookup built in commands - let builtInActionDescriptor = (Registry.as(ActionExtensions.WorkbenchActions)).getWorkbenchAction(id); + const builtInActionDescriptor = Registry.as(ActionExtensions.WorkbenchActions).getWorkbenchAction(id); if (builtInActionDescriptor) { - let action = this.instantiationService.createInstance(builtInActionDescriptor.syncDescriptor); + const action = this.instantiationService.createInstance(builtInActionDescriptor.syncDescriptor); if (action.enabled) { this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'status bar' }); @@ -275,8 +275,8 @@ class StatusBarEntryItem implements IStatusbarItem { } // Maintain old behaviour of always focusing the editor here - let activeEditor = this.editorService.getActiveEditor(); - let codeEditor = getCodeEditor(activeEditor); + const activeEditor = this.editorService.getActiveEditor(); + const codeEditor = getCodeEditor(activeEditor); if (codeEditor) { codeEditor.focus(); } diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index f39bf7369bc..eab5164b635 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -100,7 +100,7 @@ export abstract class ViewerViewlet extends Viewlet { } // Make sure the current selected element is revealed - let selection = this.viewer.getSelection(); + const selection = this.viewer.getSelection(); if (selection.length > 0) { this.reveal(selection[0], 0.5).done(null, errors.onUnexpectedError); } @@ -199,7 +199,7 @@ export class ViewletRegistry extends CompositeRegistry { * Returns an array of registered viewlets known to the platform. */ public getViewlets(): ViewletDescriptor[] { - return this.getComposits() as ViewletDescriptor[]; + return this.getComposites() as ViewletDescriptor[]; } /** @@ -246,7 +246,7 @@ export class ToggleViewletAction extends Action { } // Otherwise pass focus to editor if possible - let editor = this.editorService.getActiveEditor(); + const editor = this.editorService.getActiveEditor(); if (editor) { editor.focus(); } @@ -255,14 +255,14 @@ export class ToggleViewletAction extends Action { } private otherViewletShowing(): boolean { - let activeViewlet = this.viewletService.getActiveViewlet(); + const activeViewlet = this.viewletService.getActiveViewlet(); return !activeViewlet || activeViewlet.getId() !== this.viewletId; } private sidebarHasFocus(): boolean { - let activeViewlet = this.viewletService.getActiveViewlet(); - let activeElement = document.activeElement; + const activeViewlet = this.viewletService.getActiveViewlet(); + const activeElement = document.activeElement; return activeViewlet && activeElement && DOM.isAncestor(activeElement, (activeViewlet).getContainer().getHTMLElement()); } @@ -566,7 +566,7 @@ export abstract class CollapsibleViewletView extends CollapsibleView implements } function renderViewTree(container: HTMLElement): HTMLElement { - let treeContainer = document.createElement('div'); + const treeContainer = document.createElement('div'); container.appendChild(treeContainer); return treeContainer; @@ -596,7 +596,7 @@ function focus(tree: ITree): void { } // Make sure the current selected element is revealed - let selection = tree.getSelection(); + const selection = tree.getSelection(); if (selection.length > 0) { reveal(tree, selection[0], 0.5).done(null, errors.onUnexpectedError); } diff --git a/src/vs/workbench/common/actionRegistry.ts b/src/vs/workbench/common/actionRegistry.ts index 1e904a5ab78..fa3b8578ea4 100644 --- a/src/vs/workbench/common/actionRegistry.ts +++ b/src/vs/workbench/common/actionRegistry.ts @@ -175,7 +175,7 @@ export function triggerAndDisposeAction(instantitationService: IInstantiationSer // run action when workbench is created return partService.joinCreation().then(() => { try { - return TPromise.as(actionInstance.run(args)).then(() => { + return TPromise.as(actionInstance.run()).then(() => { actionInstance.dispose(); }, (err) => { actionInstance.dispose(); diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index 69955150e86..ebc1f02415a 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -20,26 +20,6 @@ export interface IWorkbenchComponent extends IDisposable { */ getId(): string; - /** - * Returns a JSON Object that represents the data of this memento. The optional - * parameter scope allows to specify the scope of the memento to load. If not - * provided, the scope will be global, Scope.WORKSPACE can be used to - * scope the memento to the workspace. - * - * Mementos are shared across components with the same id. This means that multiple components - * with the same id will store data into the same data structure. - */ - getMemento(storageService: IStorageService, scope?: Scope): any; - - /** - * Saves all data of the mementos that have been loaded to the local storage. This includes - * global and workspace scope. - * - * Mementos are shared across components with the same id. This means that multiple components - * with the same id will store data into the same data structure. - */ - saveMemento(): void; - /** * Called when the browser containing the container is closed. * @@ -61,12 +41,13 @@ export class WorkbenchComponent extends Disposable implements IWorkbenchComponen constructor(id: string) { super(); + this._toUnbind = []; this.id = id; this.componentMemento = new Memento(this.id); } - public get toUnbind() { + protected get toUnbind() { return this._toUnbind; } @@ -74,11 +55,27 @@ export class WorkbenchComponent extends Disposable implements IWorkbenchComponen return this.id; } - public getMemento(storageService: IStorageService, scope: Scope = Scope.GLOBAL): any { + /** + * Returns a JSON Object that represents the data of this memento. The optional + * parameter scope allows to specify the scope of the memento to load. If not + * provided, the scope will be global, Scope.WORKSPACE can be used to + * scope the memento to the workspace. + * + * Mementos are shared across components with the same id. This means that multiple components + * with the same id will store data into the same data structure. + */ + protected getMemento(storageService: IStorageService, scope: Scope = Scope.GLOBAL): any { return this.componentMemento.getMemento(storageService, scope); } - public saveMemento(): void { + /** + * Saves all data of the mementos that have been loaded to the local storage. This includes + * global and workspace scope. + * + * Mementos are shared across components with the same id. This means that multiple components + * with the same id will store data into the same data structure. + */ + protected saveMemento(): void { this.componentMemento.saveMemento(); } @@ -90,6 +87,7 @@ export class WorkbenchComponent extends Disposable implements IWorkbenchComponen public dispose(): void { this._toUnbind = dispose(this._toUnbind); + super.dispose(); } } \ No newline at end of file diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 927e46b9ef1..dc18cc01c1f 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -16,6 +16,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const TextCompareEditorVisible = new RawContextKey('textCompareEditorVisible', false); export enum ConfirmResult { SAVE, @@ -580,7 +583,7 @@ export class TextEditorOptions extends EditorOptions { public static from(input: IBaseResourceInput): TextEditorOptions { let options: TextEditorOptions = null; if (input && input.options) { - if (input.options.selection || input.options.forceOpen || input.options.revealIfVisible || input.options.preserveFocus || input.options.pinned || input.options.inactive || typeof input.options.index === 'number') { + if (input.options.selection || input.options.viewState || input.options.forceOpen || input.options.revealIfVisible || input.options.preserveFocus || input.options.pinned || input.options.inactive || typeof input.options.index === 'number') { options = new TextEditorOptions(); } @@ -613,6 +616,10 @@ export class TextEditorOptions extends EditorOptions { options.revealInCenterIfOutsideViewport = true; } + if (input.options.viewState) { + options.editorViewState = input.options.viewState; + } + if (typeof input.options.index === 'number') { options.index = input.options.index; } diff --git a/src/vs/workbench/common/editor/editorStacksModel.ts b/src/vs/workbench/common/editor/editorStacksModel.ts index 9998b376d9a..94bad196395 100644 --- a/src/vs/workbench/common/editor/editorStacksModel.ts +++ b/src/vs/workbench/common/editor/editorStacksModel.ts @@ -559,7 +559,7 @@ export class EditorGroup implements IEditorGroup { // It is possible to have the same resource opened twice (once as normal input and once as diff input) // So we need to do ref counting on the resource to provide the correct picture let counter = this.mapResourceToEditorCount[resource.toString()] || 0; - let newCounter; + let newCounter: number; if (remove) { if (counter > 1) { newCounter = counter - 1; diff --git a/src/vs/workbench/common/editor/rangeDecorations.ts b/src/vs/workbench/common/editor/rangeDecorations.ts index 17e06a72c53..62c1ccd9ec1 100644 --- a/src/vs/workbench/common/editor/rangeDecorations.ts +++ b/src/vs/workbench/common/editor/rangeDecorations.ts @@ -62,7 +62,8 @@ export class RangeHighlightDecorations implements IDisposable { this.editor = editor; this.editorDisposables.push(this.editor.onDidChangeCursorPosition((e: editorCommon.ICursorPositionChangedEvent) => { if ( - e.reason === editorCommon.CursorChangeReason.Explicit + e.reason === editorCommon.CursorChangeReason.NotSet + || e.reason === editorCommon.CursorChangeReason.Explicit || e.reason === editorCommon.CursorChangeReason.Undo || e.reason === editorCommon.CursorChangeReason.Redo ) { @@ -95,8 +96,10 @@ export class RangeHighlightDecorations implements IDisposable { } public dispose() { - this.removeHighlightRange(); - this.disposeEditorListeners(); - this.editor = null; + if (this.editor && this.editor.getModel()) { + this.removeHighlightRange(); + this.disposeEditorListeners(); + this.editor = null; + } } } \ No newline at end of file diff --git a/src/vs/workbench/common/editor/stringEditorInput.ts b/src/vs/workbench/common/editor/stringEditorInput.ts index 87447fb85d9..bf5b0489b87 100644 --- a/src/vs/workbench/common/editor/stringEditorInput.ts +++ b/src/vs/workbench/common/editor/stringEditorInput.ts @@ -74,44 +74,6 @@ export class StringEditorInput extends EditorInput { } } - /** - * Clears the textual value of this input and will also update the underlying model if this input is resolved. - */ - public clearValue(): void { - this.value = ''; - if (this.cachedModel) { - this.cachedModel.clearValue(); - } - } - - /** - * Appends to the textual value of this input and will also update the underlying model if this input is resolved. - */ - public append(value: string): void { - this.value += value; - if (this.cachedModel) { - this.cachedModel.append(value); - } - } - - /** - * Removes all lines from the top if the line number exceeds the given line count. Returns the new value if lines got trimmed. - * - * Note: This method is a no-op if the input has not yet been resolved. - */ - public trim(linecount: number): string { - if (this.cachedModel) { - let newValue = this.cachedModel.trim(linecount); - if (newValue !== null) { - this.value = newValue; - - return this.value; - } - } - - return null; - } - public resolve(refresh?: boolean): TPromise { // Use Cached Model diff --git a/src/vs/workbench/common/editor/stringEditorModel.ts b/src/vs/workbench/common/editor/stringEditorModel.ts index 82ba83b27a3..bee71a6bb7b 100644 --- a/src/vs/workbench/common/editor/stringEditorModel.ts +++ b/src/vs/workbench/common/editor/stringEditorModel.ts @@ -8,11 +8,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { EditorModel } from 'vs/workbench/common/editor'; import URI from 'vs/base/common/uri'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; /** * An editor model whith an in-memory, readonly content that is not backed by any particular resource. @@ -53,52 +50,6 @@ export class StringEditorModel extends BaseTextEditorModel { } } - /** - * Appends value to this string editor model. - */ - public append(value: string): void { - this.value += value; - if (this.textEditorModel) { - let model = this.textEditorModel; - let lastLine = model.getLineCount(); - let lastLineMaxColumn = model.getLineMaxColumn(lastLine); - - model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), value)]); - } - } - - /** - * Clears the value of this string editor model - */ - public clearValue(): void { - this.value = ''; - if (this.textEditorModel) { - let model = this.textEditorModel; - let lastLine = model.getLineCount(); - model.applyEdits([EditOperation.delete(new Range(1, 1, lastLine, model.getLineMaxColumn(lastLine)))]); - } - } - - /** - * Removes all lines from the top if the line number exceeds the given line count. Returns the new value if lines got trimmed. - */ - public trim(linecount: number): string { - if (this.textEditorModel) { - let model = this.textEditorModel; - let lastLine = model.getLineCount(); - if (lastLine > linecount) { - model.applyEdits([EditOperation.delete(new Range(1, 1, lastLine - linecount + 1, 1))]); - - let newValue = model.getValue(); - this.value = newValue; - - return this.value; - } - } - - return null; - } - public load(): TPromise { // Create text editor model if not yet done diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 56c18f7f7fb..c6e90d4abbd 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -24,8 +24,8 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd private modelDisposeListener: IDisposable; constructor( - @IModelService private modelService: IModelService, - @IModeService private modeService: IModeService, + @IModelService protected modelService: IModelService, + @IModeService protected modeService: IModeService, textEditorModelHandle?: URI ) { super(); @@ -100,7 +100,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd return this; } - private getFirstLineText(value: string | IRawText): string { + protected getFirstLineText(value: string | IRawText): string { if (typeof value === 'string') { const firstLineText = value.substr(0, 100); @@ -153,20 +153,6 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd this.textEditorModel.setValueFromRawText(rawText); } - /** - * Updates the text editor model mode based on the settings and configuration. - */ - protected updateTextEditorModelMode(modeId?: string): void { - if (!this.textEditorModel) { - return; - } - - const firstLineText = this.getFirstLineText(this.textEditorModel.getValue()); - const mode = this.getOrCreateMode(this.modeService, modeId, firstLineText); - - this.modelService.setMode(this.textEditorModel, mode); - } - /** * Returns the textual value of this editor model or null if it has not yet been created. */ @@ -179,6 +165,10 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd return null; } + public isResolved(): boolean { + return !!this.textEditorModelHandle; + } + public dispose(): void { if (this.modelDisposeListener) { this.modelDisposeListener.dispose(); // dispose this first because it will trigger another dispose() otherwise @@ -194,8 +184,4 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd super.dispose(); } - - public isResolved(): boolean { - return !!this.textEditorModelHandle; - } } \ No newline at end of file diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index 6d4507fa1f4..9f9679e786c 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -7,7 +7,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { EditorModel, IEncodingSupport } from 'vs/workbench/common/editor'; -import { StringEditorModel } from 'vs/workbench/common/editor/stringEditorModel'; +import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import URI from 'vs/base/common/uri'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { EndOfLinePreference } from 'vs/editor/common/editorCommon'; @@ -21,7 +21,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -export class UntitledEditorModel extends StringEditorModel implements IEncodingSupport { +export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport { public static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY; @@ -43,8 +43,8 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS private hasAssociatedFilePath: boolean; constructor( - modeId: string, - resource: URI, + private modeId: string, + private resource: URI, hasAssociatedFilePath: boolean, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @@ -52,7 +52,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS @ITextFileService private textFileService: ITextFileService, @IConfigurationService private configurationService: IConfigurationService ) { - super('', modeId, resource, modeService, modelService); + super(modelService, modeService); this.hasAssociatedFilePath = hasAssociatedFilePath; this.dirty = false; @@ -167,13 +167,11 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS return null; }).then(backupContent => { - if (backupContent) { - this.setValue(backupContent); - } - this.setDirty(this.hasAssociatedFilePath || !!backupContent); // untitled associated to file path are dirty right away as well as untitled with content + // untitled associated to file path are dirty right away as well as untitled with content + this.setDirty(this.hasAssociatedFilePath || !!backupContent); - return super.load().then(model => { + return this.doLoad(backupContent || '').then(model => { const configuration = this.configurationService.getConfiguration(); // Encoding @@ -187,6 +185,21 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS }); } + private doLoad(content: string): TPromise { + + // Create text editor model if not yet done + if (!this.textEditorModel) { + return this.createTextEditorModel(content, this.resource, this.modeId); + } + + // Otherwise update + else { + this.updateTextEditorModel(content); + } + + return TPromise.as(this); + } + private onModelContentChanged(): void { this.versionId++; diff --git a/src/vs/workbench/common/panel.ts b/src/vs/workbench/common/panel.ts index 57c365b0a62..2ac319835f1 100644 --- a/src/vs/workbench/common/panel.ts +++ b/src/vs/workbench/common/panel.ts @@ -2,6 +2,7 @@ * 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 { IComposite } from 'vs/workbench/common/composite'; diff --git a/src/vs/workbench/common/viewlet.ts b/src/vs/workbench/common/viewlet.ts index 9ef11ebf92f..1d6b901f78e 100644 --- a/src/vs/workbench/common/viewlet.ts +++ b/src/vs/workbench/common/viewlet.ts @@ -2,10 +2,12 @@ * 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 { IComposite } from 'vs/workbench/common/composite'; export interface IViewlet extends IComposite { + /** * Returns the minimal width needed to avoid any content horizontal truncation */ diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index a29af01a04e..3c1e03ac623 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -371,7 +371,8 @@ export class ShowStartupPerformance extends Action { const metrics: IStartupMetrics = this.timerService.startupMetrics; if (metrics.initialStartup) { - table.push({ Topic: '[main] start => window.loadUrl()', 'Took (ms)': metrics.timers.ellapsedWindowLoad }); + table.push({ Topic: '[main] start => app.isReady', 'Took (ms)': metrics.timers.ellapsedAppReady }); + table.push({ Topic: '[main] app.isReady => window.loadUrl()', 'Took (ms)': metrics.timers.ellapsedWindowLoad }); } table.push({ Topic: '[renderer] window.loadUrl() => begin to require(workbench.main.js)', 'Took (ms)': metrics.timers.ellapsedWindowLoadToRequire }); @@ -588,8 +589,8 @@ export class OpenRecentAction extends Action { } const runPick = (path: string, context: IEntryRunContext) => { - const newWindow = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0; - this.windowsService.windowOpen([path], newWindow); + const forceNewWindow = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0; + this.windowsService.openWindow([path], { forceNewWindow }); }; const folderPicks: IFilePickOpenEntry[] = recentFolders.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('folders', "folders") } : void 0, true)); diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index b03d213e348..5d438e137a2 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -16,24 +16,7 @@ const ipc = electron.ipcRenderer; process.lazyEnv = new Promise(function (resolve) { - - const origEnv = process.env; - - // warn about missing environment variables - // while we are resolve lazyEnv - process.env = new Proxy(origEnv, { - get: function (target, name) { - const result = target[name]; - if (typeof result === 'undefined') { - console.warn('process.env[\'' + name + '\'] is undefined AND \'process.lazyEnv\' is NOT READY yet.'); - } - return result; - } - }); - ipc.once('vscode:acceptShellEnv', function (event, shellEnv) { - // store process.env, mixin shellEnv, done - process.env = origEnv; assign(process.env, shellEnv); resolve(process.env); }); @@ -202,6 +185,7 @@ function main() { isInitialStartup: !!configuration.isInitialStartup, hasAccessibilitySupport: !!configuration.accessibilitySupport, start: new Date(configuration.perfStartTime), + appReady: new Date(configuration.perfAppReady), windowLoad: new Date(configuration.perfWindowLoadTime), beforeLoadWorkbenchMain: new Date() }; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 8439146a057..8be72f39727 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -171,9 +171,36 @@ configurationRegistry.registerConfiguration({ // Configuration: Window let properties: { [path: string]: IJSONSchema; } = { 'window.openFilesInNewWindow': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('openFilesInNewWindow', "When enabled, will open files in a new window instead of reusing an existing instance.") + 'type': 'string', + 'enum': ['on', 'off', 'default'], + 'default': 'default', + 'description': platform.isMacintosh ? + nls.localize('openFilesInNewWindowMac', + `Controls if files should open in a new window or the last active window. +- default: files will open in the last active window unless opened via the dock or from finder +- on: files will open in a new window +- off: files will open in the last active window +Note that there can still be cases where this setting is ignored (e.g. when using the -new-window or -reuse-window command line option).` + ) : + nls.localize('openFilesInNewWindow', + `Controls if files should open in a new window or the last active window. +- default: files will open in the last active window +- on: files will open in a new window +- off: files will open in the last active window +Note that there can still be cases where this setting is ignored (e.g. when using the -new-window or -reuse-window command line option).` + ) + }, + 'window.openFoldersInNewWindow': { + 'type': 'string', + 'enum': ['on', 'off', 'default'], + 'default': 'default', + 'description': nls.localize('openFoldersInNewWindow', + `Controls if folders should open in a new window or the last active window. +- default: folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu) +- on: folders will open in a new window +- off: folders will open in the last active window +Note that there can still be cases where this setting is ignored (e.g. when using the -new-window or -reuse-window command line option).` + ) }, 'window.reopenFolders': { 'type': 'string', @@ -221,4 +248,29 @@ configurationRegistry.registerConfiguration({ 'title': nls.localize('windowConfigurationTitle', "Window"), 'type': 'object', 'properties': properties -}); \ No newline at end of file +}); + +// Configuration: Zen Mode +configurationRegistry.registerConfiguration({ + 'id': 'zenMode', + 'order': 9, + 'title': nls.localize('zenModeConfigurationTitle', "Zen Mode"), + 'type': 'object', + 'properties': { + 'zenMode.fullScreen': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.fullScreen', "Controls if turning on Zen Mode also puts the workbench into full screen mode.") + }, + 'zenMode.hideTabs': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.hideTabs', "Controls if turning on Zen Mode also hides workbench tabs.") + }, + 'zenMode.hideStatusBar': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.hideStatusBar', "Controls if turning on Zen Mode also hides the status bar at the bottom of the workbench.") + } + } +}); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index cc391d5aa7d..4f2a20f0373 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -14,7 +14,6 @@ import { domContentLoaded } from 'vs/base/browser/dom'; import errors = require('vs/base/common/errors'); import platform = require('vs/base/common/platform'); import paths = require('vs/base/common/paths'); -import timer = require('vs/base/common/timer'); import uri from 'vs/base/common/uri'; import strings = require('vs/base/common/strings'); import { IResourceInput } from 'vs/platform/editor/common/editor'; @@ -62,10 +61,6 @@ export function startup(configuration: IWindowConfiguration): TPromise { filesToDiff }; - if (configuration.performance) { - timer.ENABLE_TIMER = true; - } - // Resolve workspace return getWorkspace(configuration.workspacePath).then(workspace => { @@ -171,4 +166,4 @@ function loaderError(err: Error): Error { } return new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err))); -} \ No newline at end of file +} diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 51a0464b43b..1f1d712bb9d 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -137,7 +137,7 @@ export class ElectronWindow { DOM.EventHelper.stop(e, true); this.focus(); // make sure this window has focus so that the open call reaches the right window! - this.windowsService.windowOpen(draggedExternalResources.map(r => r.fsPath)); + this.windowsService.openWindow(draggedExternalResources.map(r => r.fsPath), { forceReuseWindow: true }); cleanUp(); }) @@ -380,7 +380,7 @@ export class ElectronWindow { // In diffMode we open 2 resources as diff if (diffMode && resources.length === 2) { - return this.editorService.openEditor({ leftResource: resources[0].resource, rightResource: resources[1].resource }); + return this.editorService.openEditor({ leftResource: resources[0].resource, rightResource: resources[1].resource, options: { pinned: true } }); } // For one file, just put it into the current active editor diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index 00480dbd261..8ff9a3ddd07 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -86,8 +86,6 @@ import 'vs/workbench/parts/execution/electron-browser/terminal.contribution'; import 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; -import 'vs/workbench/parts/contentprovider/common/contentprovider.contribution'; - import 'vs/workbench/parts/themes/electron-browser/themes.contribution'; import 'vs/workbench/parts/feedback/electron-browser/feedback.contribution'; @@ -107,6 +105,4 @@ import 'vs/workbench/electron-browser/main'; import 'vs/workbench/parts/themes/test/electron-browser/themes.test.contribution'; -import 'vs/workbench/parts/watermark/electron-browser/watermark'; - -import 'vs/workbench/parts/viewpicker/browser/viewpicker.contribution'; +import 'vs/workbench/parts/watermark/electron-browser/watermark'; \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index a903d8590d3..ff5e2429ae5 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -41,7 +41,7 @@ import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickO import { getServices } from 'vs/platform/instantiation/common/extensions'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { WorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { Position, Parts, IPartService, ILayoutOptions, IZenModeOptions } from 'vs/workbench/services/part/common/partService'; +import { Position, Parts, IPartService, ILayoutOptions } from 'vs/workbench/services/part/common/partService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ContextMenuService } from 'vs/workbench/services/contextview/electron-browser/contextmenuService'; @@ -97,6 +97,12 @@ interface WorkbenchParams { serviceCollection: ServiceCollection; } +interface IZenModeSettings { + fullScreen: boolean; + hideTabs: boolean; + hideStatusBar: boolean; +} + export interface IWorkbenchStartedInfo { customKeybindingsCount: number; restoreViewletDuration: number; @@ -127,6 +133,7 @@ export class Workbench implements IPartService { private static sidebarHiddenSettingKey = 'workbench.sidebar.hidden'; private static sidebarRestoreSettingKey = 'workbench.sidebar.restore'; private static panelHiddenSettingKey = 'workbench.panel.hidden'; + private static zenModeActiveSettingKey = 'workbench.zenmode.active'; private static sidebarPositionConfigurationKey = 'workbench.sideBar.location'; private static statusbarVisibleConfigurationKey = 'workbench.statusBar.visible'; @@ -262,7 +269,7 @@ export class Workbench implements IPartService { // Workbench Layout this.createWorkbenchLayout(); - // Load composits and editors in parallel + // Load composites and editors in parallel const compositeAndEditorPromises: TPromise[] = []; // Restore last opened viewlet @@ -315,6 +322,10 @@ export class Workbench implements IPartService { }); })); + if (this.storageService.getBoolean(Workbench.zenModeActiveSettingKey, StorageScope.WORKSPACE, false)) { + this.toggleZenMode(true); + } + // Flag workbench as created once done const workbenchDone = (error?: Error) => { this.workbenchCreated = true; @@ -357,7 +368,7 @@ export class Workbench implements IPartService { // Files to diff is exclusive if (filesToDiff && filesToDiff.length === 2) { - return this.editorService.createInput({ leftResource: filesToDiff[0].resource, rightResource: filesToDiff[1].resource }).then(input => [{ input }]); + return this.editorService.createInput({ leftResource: filesToDiff[0].resource, rightResource: filesToDiff[1].resource }).then(input => [{ input, options: EditorOptions.create({ pinned: true }) }]); } // Otherwise: Open/Create files @@ -367,14 +378,19 @@ export class Workbench implements IPartService { // Files to create inputs.push(...filesToCreate.map(resourceInput => this.untitledEditorService.createOrGet(resourceInput.resource))); - options.push(...filesToCreate.map(r => null)); // fill empty options for files to create because we dont have options there + options.push(...filesToCreate.map(r => EditorOptions.create({ pinned: true }))); // Files to open let filesToOpenInputPromise = filesToOpen.map(resourceInput => this.editorService.createInput(resourceInput)); return TPromise.join(filesToOpenInputPromise).then((inputsToOpen) => { inputs.push(...inputsToOpen); - options.push(...filesToOpen.map(resourceInput => TextEditorOptions.from(resourceInput))); + options.push(...filesToOpen.map(resourceInput => { + const options: EditorOptions = TextEditorOptions.from(resourceInput) || EditorOptions.create({ pinned: true }); + options.pinned = true; + + return options; + })); return inputs.map((input, index) => { return { input, options: options[index] }; }); }); @@ -793,6 +809,8 @@ export class Workbench implements IPartService { if (reason === ShutdownReason.RELOAD) { this.storageService.store(Workbench.sidebarRestoreSettingKey, 'true', StorageScope.WORKSPACE); } + // Preserve zen mode only on reload. Real quit gets out of zen mode so novice users do not get stuck in zen mode. + this.storageService.store(Workbench.zenModeActiveSettingKey, reason === ShutdownReason.RELOAD && this.zenMode.active, StorageScope.WORKSPACE); // Pass shutdown on to each participant this.toShutdown.forEach(s => s.shutdown()); @@ -1044,26 +1062,24 @@ export class Workbench implements IPartService { return Identifiers.WORKBENCH_CONTAINER; } - public toggleZenMode(options?: IZenModeOptions): void { - options = options || {}; + public toggleZenMode(skipLayout?: boolean): void { this.zenMode.active = !this.zenMode.active; // Check if zen mode transitioned to full screen and if now we are out of zen mode -> we need to go out of full screen let toggleFullScreen = false; if (this.zenMode.active) { - toggleFullScreen = !browser.isFullscreen() && !options.noFullScreen; + const config = this.configurationService.getConfiguration('zenMode'); + toggleFullScreen = !browser.isFullscreen() && config.fullScreen; this.zenMode.transitionedToFullScreen = toggleFullScreen; this.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART); this.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART); this.setPanelHidden(true, true); this.setSideBarHidden(true, true); - if (!options.keepStatusBar) { + this.setActivityBarHidden(true, true); + if (config.hideStatusBar) { this.setStatusBarHidden(true, true); } - if (!options.keepActivityBar) { - this.setActivityBarHidden(true, true); - } - if (!options.keepTabs) { + if (config.hideTabs) { this.editorPart.hideTabs(true); } } else { @@ -1084,7 +1100,9 @@ export class Workbench implements IPartService { } this.inZenMode.set(this.zenMode.active); - this.layout(); + if (!skipLayout) { + this.layout(); + } if (toggleFullScreen) { this.windowService.toggleFullScreen().done(undefined, errors.onUnexpectedError); } diff --git a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts index 2177ef468c9..43170f0f222 100644 --- a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts @@ -249,10 +249,10 @@ class DarwinCLIHelper implements IWorkbenchContribution { if (process.platform === 'darwin') { const category = nls.localize('shellCommand', "Shell Command"); - const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); + const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallAction, InstallAction.ID, InstallAction.LABEL), 'Shell Command: Install \'code\' command in PATH', category); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(UninstallAction, UninstallAction.ID, UninstallAction.LABEL), 'Shell Command: Uninstall \'code\' command from PATH', category); - const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); + const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(DarwinCLIHelper); } diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 2f2f422e136..997807ba6e9 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -717,15 +717,7 @@ export class FocusProcessAction extends AbstractDebugAction { return this.debugService.focusStackFrameAndEvaluate(null, process).then(() => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; if (stackFrame) { - return this.editorService.openEditor({ - resource: stackFrame.source.uri, - options: { - preserveFocus: true, - selection: { startLineNumber: stackFrame.lineNumber, startColumn: 1 }, - revealIfVisible: true, - revealInCenterIfOutsideViewport: true - } - }); + return stackFrame.openInEditor(this.editorService, true); } }); } diff --git a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts index e74b027c188..ceb3ae21200 100644 --- a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts +++ b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as lifecycle from 'vs/base/common/lifecycle'; import uri from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -10,13 +11,15 @@ import { IModel } from 'vs/editor/common/editorCommon'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { DEBUG_SCHEME, IDebugService } from 'vs/workbench/parts/debug/common/debug'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { DEBUG_SCHEME, IDebugService, State } from 'vs/workbench/parts/debug/common/debug'; import { Model } from 'vs/workbench/parts/debug/common/debugModel'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; export class DebugContentProvider implements IWorkbenchContribution, ITextModelContentProvider { + private modelsToDispose: IModel[]; + constructor( @ITextModelResolverService textModelResolverService: ITextModelResolverService, @IDebugService private debugService: IDebugService, @@ -24,6 +27,12 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC @IModeService private modeService: IModeService ) { textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this); + this.modelsToDispose = []; + this.debugService.onDidChangeState(() => { + if (this.debugService.state === State.Inactive) { + this.modelsToDispose = lifecycle.dispose(this.modelsToDispose); + } + }); } public getId(): string { @@ -39,8 +48,10 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC return process.session.source({ sourceReference: Source.getSourceReference(resource) }).then(response => { const mime = response.body.mimeType || guessMimeTypes(resource.toString())[0]; const modePromise = this.modeService.getOrCreateMode(mime); + const model = this.modelService.createModel(response.body.content, modePromise, resource); + this.modelsToDispose.push(model); - return this.modelService.createModel(response.body.content, modePromise, resource); + return model; }, err => { (this.debugService.getModel()).sourceIsUnavailable(resource); return err; diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts index dac4d20c36e..258c0a29a13 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts @@ -18,7 +18,7 @@ interface IDebugEditorModelData { toDispose: lifecycle.IDisposable[]; breakpointDecorationIds: string[]; breakpointLines: number[]; - breakpointDecorationsAsMap: { [decorationId: string]: boolean; }; + breakpointDecorationsAsMap: Map; currentStackDecorations: string[]; topStackFrameRange: IRange; dirty: boolean; @@ -29,16 +29,14 @@ const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; export class DebugEditorModelManager implements IWorkbenchContribution { static ID = 'breakpointManager'; - private modelData: { - [modelUrl: string]: IDebugEditorModelData; - }; + private modelDataMap: Map; private toDispose: lifecycle.IDisposable[]; constructor( @IModelService private modelService: IModelService, @IDebugService private debugService: IDebugService ) { - this.modelData = Object.create(null); + this.modelDataMap = new Map(); this.toDispose = []; this.registerListeners(); } @@ -48,14 +46,14 @@ export class DebugEditorModelManager implements IWorkbenchContribution { } public dispose(): void { - Object.keys(this.modelData).forEach(modelUriStr => { - lifecycle.dispose(this.modelData[modelUriStr].toDispose); - this.modelData[modelUriStr].model.deltaDecorations(this.modelData[modelUriStr].breakpointDecorationIds, []); - this.modelData[modelUriStr].model.deltaDecorations(this.modelData[modelUriStr].currentStackDecorations, []); + this.modelDataMap.forEach(modelData => { + lifecycle.dispose(modelData.toDispose); + modelData.model.deltaDecorations(modelData.breakpointDecorationIds, []); + modelData.model.deltaDecorations(modelData.currentStackDecorations, []); }); this.toDispose = lifecycle.dispose(this.toDispose); - this.modelData = null; + this.modelDataMap.clear(); } private registerListeners(): void { @@ -67,7 +65,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame())); this.toDispose.push(this.debugService.onDidChangeState(() => { if (this.debugService.state === State.Inactive) { - Object.keys(this.modelData).forEach(key => this.modelData[key].dirty = false); + this.modelDataMap.forEach(modelData => modelData.dirty = false); } })); } @@ -80,40 +78,41 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const breakPointDecorations = model.deltaDecorations([], this.createBreakpointDecorations(breakpoints)); const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUrlStr, e))]; + const breakpointDecorationsAsMap = new Map(); + breakPointDecorations.forEach(bpd => breakpointDecorationsAsMap.set(bpd, true)); - this.modelData[modelUrlStr] = { + this.modelDataMap.set(modelUrlStr, { model: model, toDispose: toDispose, breakpointDecorationIds: breakPointDecorations, breakpointLines: breakpoints.map(bp => bp.lineNumber), - breakpointDecorationsAsMap: objects.toObject(breakPointDecorations, key => key, key => true), + breakpointDecorationsAsMap, currentStackDecorations: currentStackDecorations, topStackFrameRange: null, dirty: false - }; + }); } private onModelRemoved(model: IModel): void { const modelUriStr = model.uri.toString(); - if (this.modelData[modelUriStr]) { - lifecycle.dispose(this.modelData[modelUriStr].toDispose); - delete this.modelData[modelUriStr]; + if (this.modelDataMap.has(modelUriStr)) { + lifecycle.dispose(this.modelDataMap.get(modelUriStr).toDispose); + this.modelDataMap.delete(modelUriStr); } } // call stack management. Represent data coming from the debug service. private onFocusStackFrame(): void { - Object.keys(this.modelData).forEach(modelUrlStr => { - const modelData = this.modelData[modelUrlStr]; - modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(modelUrlStr)); + this.modelDataMap.forEach((modelData, uri) => { + modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(uri)); }); } - private createCallStackDecorations(modelUrlStr: string): IModelDeltaDecoration[] { + private createCallStackDecorations(modelUriStr: string): IModelDeltaDecoration[] { const result: IModelDeltaDecoration[] = []; const stackFrame = this.debugService.getViewModel().focusedStackFrame; - if (!stackFrame || stackFrame.source.uri.toString() !== modelUrlStr) { + if (!stackFrame || stackFrame.source.uri.toString() !== modelUriStr) { return result; } @@ -140,15 +139,16 @@ export class DebugEditorModelManager implements IWorkbenchContribution { range: wholeLineRange }); - if (this.modelData[modelUrlStr]) { - if (this.modelData[modelUrlStr].topStackFrameRange && this.modelData[modelUrlStr].topStackFrameRange.startLineNumber === wholeLineRange.startLineNumber && - this.modelData[modelUrlStr].topStackFrameRange.startColumn !== wholeLineRange.startColumn) { + if (this.modelDataMap.has(modelUriStr)) { + const modelData = this.modelDataMap.get(modelUriStr); + if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === wholeLineRange.startLineNumber && + modelData.topStackFrameRange.startColumn !== wholeLineRange.startColumn) { result.push({ options: DebugEditorModelManager.TOP_STACK_FRAME_COLUMN_DECORATION, range: wholeLineRange }); } - this.modelData[modelUrlStr].topStackFrameRange = wholeLineRange; + modelData.topStackFrameRange = wholeLineRange; } } } else { @@ -168,22 +168,21 @@ export class DebugEditorModelManager implements IWorkbenchContribution { // breakpoints management. Represent data coming from the debug service and also send data back. private onModelDecorationsChanged(modelUrlStr: string, e: IModelDecorationsChangedEvent): void { - const modelData = this.modelData[modelUrlStr]; - let myDecorationsCount = Object.keys(modelData.breakpointDecorationsAsMap).length; - if (myDecorationsCount === 0) { + const modelData = this.modelDataMap.get(modelUrlStr); + if (modelData.breakpointDecorationsAsMap.size === 0) { // I have no decorations return; } - if (!e.changedDecorations.some(decorationId => modelData.breakpointDecorationsAsMap[decorationId])) { + if (!e.changedDecorations.some(decorationId => modelData.breakpointDecorationsAsMap.has(decorationId))) { // nothing to do, my decorations did not change. return; } const data: IRawBreakpoint[] = []; - const lineToBreakpointDataMap: { [key: number]: IBreakpoint } = {}; + const lineToBreakpointDataMap = new Map(); this.debugService.getModel().getBreakpoints().filter(bp => bp.uri.toString() === modelUrlStr).forEach(bp => { - lineToBreakpointDataMap[bp.lineNumber] = bp; + lineToBreakpointDataMap.set(bp.lineNumber, bp); }); const modelUri = modelData.model.uri; @@ -192,13 +191,14 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const lineNumber = modelData.breakpointLines[i]; // check if the line got deleted. if (decorationRange.endColumn - decorationRange.startColumn > 0) { + const breakpoint = lineToBreakpointDataMap.get(lineNumber); // since we know it is collapsed, it cannot grow to multiple lines data.push({ lineNumber: decorationRange.startLineNumber, - enabled: lineToBreakpointDataMap[lineNumber].enabled, - condition: lineToBreakpointDataMap[lineNumber].condition, - hitCondition: lineToBreakpointDataMap[lineNumber].hitCondition, - column: lineToBreakpointDataMap[lineNumber].column + enabled: breakpoint.enabled, + condition: breakpoint.condition, + hitCondition: breakpoint.hitCondition, + column: breakpoint.column }); } } @@ -213,31 +213,32 @@ export class DebugEditorModelManager implements IWorkbenchContribution { } private onBreakpointsChange(): void { - const breakpointsMap: { [key: string]: IBreakpoint[] } = Object.create(null); + const breakpointsMap = new Map(); this.debugService.getModel().getBreakpoints().forEach(bp => { const uriStr = bp.uri.toString(); - if (breakpointsMap[uriStr]) { - breakpointsMap[uriStr].push(bp); + if (breakpointsMap.has(uriStr)) { + breakpointsMap.get(uriStr).push(bp); } else { - breakpointsMap[uriStr] = [bp]; + breakpointsMap.set(uriStr, [bp]); } }); - Object.keys(breakpointsMap).forEach(modelUriStr => { - if (this.modelData[modelUriStr]) { - this.updateBreakpoints(this.modelData[modelUriStr], breakpointsMap[modelUriStr]); + breakpointsMap.forEach((bps, uri) => { + if (this.modelDataMap.has(uri)) { + this.updateBreakpoints(this.modelDataMap.get(uri), breakpointsMap.get(uri)); } }); - Object.keys(this.modelData).forEach(modelUriStr => { - if (!breakpointsMap[modelUriStr]) { - this.updateBreakpoints(this.modelData[modelUriStr], []); + this.modelDataMap.forEach((modelData, uri) => { + if (!breakpointsMap.has(uri)) { + this.updateBreakpoints(modelData, []); } }); } private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void { modelData.breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorationIds, this.createBreakpointDecorations(newBreakpoints)); - modelData.breakpointDecorationsAsMap = objects.toObject(modelData.breakpointDecorationIds, key => key, (key) => true); + modelData.breakpointDecorationsAsMap.clear(); + modelData.breakpointDecorationIds.forEach(id => modelData.breakpointDecorationsAsMap.set(id, true)); modelData.breakpointLines = newBreakpoints.map(bp => bp.lineNumber); } @@ -254,7 +255,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const activated = this.debugService.getModel().areBreakpointsActivated(); const state = this.debugService.state; const debugActive = state === State.Running || state === State.Stopped || state === State.Initializing; - const modelData = this.modelData[breakpoint.uri.toString()]; + const modelData = this.modelDataMap.get(breakpoint.uri.toString()); let result = (!breakpoint.enabled || !activated) ? DebugEditorModelManager.BREAKPOINT_DISABLED_DECORATION : debugActive && modelData && modelData.dirty && !breakpoint.verified ? DebugEditorModelManager.BREAKPOINT_DIRTY_DECORATION : diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 86188d6eb8d..c665252b3e5 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -14,6 +14,7 @@ import { ISuggestion } from 'vs/editor/common/modes'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { Range } from 'vs/editor/common/core/range'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; export const VIEWLET_ID = 'workbench.view.debug'; export const REPL_ID = 'workbench.panel.repl'; @@ -170,6 +171,7 @@ export interface IStackFrame extends ITreeElement { getScopes(): TPromise; restart(): TPromise; toString(): string; + openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise; } export interface IEnablement extends ITreeElement { diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 23a4ebba24f..5b97853ef25 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -19,6 +19,7 @@ import { ISuggestion } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import * as debug from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; const MAX_REPL_LENGTH = 10000; const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -71,7 +72,7 @@ export class OutputNameValueElement extends AbstractOutputElement implements deb } public get hasChildren(): boolean { - return isObject(this.valueObj) && Object.getOwnPropertyNames(this.valueObj).length > 0; + return (Array.isArray(this.valueObj) && this.valueObj.length > 0) || (isObject(this.valueObj) && Object.getOwnPropertyNames(this.valueObj).length > 0); } public getChildren(): TPromise { @@ -90,7 +91,7 @@ export class OutputNameValueElement extends AbstractOutputElement implements deb export class ExpressionContainer implements debug.IExpressionContainer { - public static allValues: { [id: string]: string } = {}; + public static allValues: Map = new Map(); // Use chunks to support variable paging #9537 private static BASE_CHUNK_SIZE = 100; @@ -173,9 +174,9 @@ export class ExpressionContainer implements debug.IExpressionContainer { public set value(value: string) { this._value = value; - this.valueChanged = ExpressionContainer.allValues[this.getId()] && - ExpressionContainer.allValues[this.getId()] !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues[this.getId()] !== value; - ExpressionContainer.allValues[this.getId()] = value; + this.valueChanged = ExpressionContainer.allValues.get(this.getId()) && + ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value; + ExpressionContainer.allValues.set(this.getId(), value); } } @@ -349,7 +350,20 @@ export class StackFrame implements debug.IStackFrame { } public toString(): string { - return `${this.name} (${this.source.name}:${this.lineNumber})`; + return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.lineNumber})`; + } + + public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise { + return editorService.openEditor({ + resource: this.source.uri, + description: this.source.origin, + options: { + preserveFocus, + selection: { startLineNumber: this.lineNumber, startColumn: 1 }, + revealIfVisible: true, + revealInCenterIfOutsideViewport: true + } + }, sideBySide); } } @@ -463,10 +477,10 @@ export class Thread implements debug.IThread { export class Process implements debug.IProcess { - private threads: { [reference: number]: Thread; }; + private threads: Map; constructor(public name: string, private _session: debug.ISession & debug.ITreeElement) { - this.threads = {}; + this.threads = new Map(); } public get session(): debug.ISession { @@ -474,11 +488,13 @@ export class Process implements debug.IProcess { } public getThread(threadId: number): Thread { - return this.threads[threadId]; + return this.threads.get(threadId); } public getAllThreads(): debug.IThread[] { - return Object.keys(this.threads).map(key => this.threads[key]); + const result = []; + this.threads.forEach(t => result.push(t)); + return result; } public getId(): string { @@ -487,66 +503,66 @@ export class Process implements debug.IProcess { public rawUpdate(data: debug.IRawModelUpdate): void { - if (data.thread && !this.threads[data.threadId]) { + if (data.thread && !this.threads.has(data.threadId)) { // A new thread came in, initialize it. - this.threads[data.threadId] = new Thread(this, data.thread.name, data.thread.id); + this.threads.set(data.threadId, new Thread(this, data.thread.name, data.thread.id)); } if (data.stoppedDetails) { // Set the availability of the threads' callstacks depending on // whether the thread is stopped or not if (data.allThreadsStopped) { - Object.keys(this.threads).forEach(ref => { + this.threads.forEach(thread => { // Only update the details if all the threads are stopped // because we don't want to overwrite the details of other // threads that have stopped for a different reason - this.threads[ref].stoppedDetails = clone(data.stoppedDetails); - this.threads[ref].stopped = true; - this.threads[ref].clearCallStack(); + thread.stoppedDetails = clone(data.stoppedDetails); + thread.stopped = true; + thread.clearCallStack(); }); - } else { + } else if (this.threads.has(data.threadId)) { // One thread is stopped, only update that thread. - this.threads[data.threadId].stoppedDetails = data.stoppedDetails; - this.threads[data.threadId].clearCallStack(); - this.threads[data.threadId].stopped = true; + const thread = this.threads.get(data.threadId); + thread.stoppedDetails = data.stoppedDetails; + thread.clearCallStack(); + thread.stopped = true; } } } public clearThreads(removeThreads: boolean, reference: number = undefined): void { if (reference) { - if (this.threads[reference]) { - this.threads[reference].clearCallStack(); - this.threads[reference].stoppedDetails = undefined; - this.threads[reference].stopped = false; + if (this.threads.has(reference)) { + const thread = this.threads.get(reference); + thread.clearCallStack(); + thread.stoppedDetails = undefined; + thread.stopped = false; if (removeThreads) { - delete this.threads[reference]; + this.threads.delete(reference); } } } else { - Object.keys(this.threads).forEach(ref => { - this.threads[ref].clearCallStack(); - this.threads[ref].stoppedDetails = undefined; - this.threads[ref].stopped = false; + this.threads.forEach(thread => { + thread.clearCallStack(); + thread.stoppedDetails = undefined; + thread.stopped = false; }); if (removeThreads) { - this.threads = {}; - ExpressionContainer.allValues = {}; + this.threads.clear(); + ExpressionContainer.allValues.clear(); } } } public sourceIsUnavailable(uri: uri): void { - Object.keys(this.threads).forEach(key => { - if (this.threads[key].getCachedCallStack()) { - this.threads[key].getCachedCallStack().forEach(stackFrame => { - if (stackFrame.source.uri.toString() === uri.toString()) { - stackFrame.source.available = false; - } - }); - } + this.threads.forEach(thread => { + thread.getCallStack().forEach(stackFrame => { + if (stackFrame.source.uri.toString() === uri.toString()) { + stackFrame.source.available = false; + } + }); }); } diff --git a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts index 790de1fcc91..21c69e61090 100644 --- a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts @@ -1042,11 +1042,11 @@ declare module DebugProtocol { /** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */ export interface Source { - /** The short name of the source. Every source returned from the debug adapter has a name. When specifying a source to the debug adapter this name is optional. */ + /** The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional. */ name?: string; - /** The long (absolute) path of the source. It is not guaranteed that the source exists at this location. */ + /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its vaule is 0). */ path?: string; - /** If sourceReference > 0 the contents of the source can be retrieved through the SourceRequest. A sourceReference is only valid for a session, so it must not be used to persist a source. */ + /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. */ sourceReference?: number; /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ origin?: string; @@ -1218,7 +1218,7 @@ declare module DebugProtocol { } /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ - export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor' | 'folder'; + export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor'; /** Names of checksum algorithms that may be supported by a debug adapter. */ export type ChecksumAlgorithm = 'MD5' | 'SHA1' | 'SHA256' | 'SHA1Normalized' | 'SHA256Normalized' | 'timestamp'; diff --git a/src/vs/workbench/parts/debug/common/debugSource.ts b/src/vs/workbench/parts/debug/common/debugSource.ts index df728f73675..e51a24cfefb 100644 --- a/src/vs/workbench/parts/debug/common/debugSource.ts +++ b/src/vs/workbench/parts/debug/common/debugSource.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import uri from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; import { DEBUG_SCHEME } from 'vs/workbench/parts/debug/common/debug'; export class Source { @@ -15,7 +14,8 @@ export class Source { private static INTERNAL_URI_PREFIX = `${DEBUG_SCHEME}://internal/`; constructor(public raw: DebugProtocol.Source, available = true) { - this.uri = raw.path ? uri.file(paths.normalize(raw.path)) : uri.parse(Source.INTERNAL_URI_PREFIX + raw.sourceReference + '/' + raw.name); + const path = raw.path || raw.name; + this.uri = raw.sourceReference > 0 ? uri.parse(Source.INTERNAL_URI_PREFIX + raw.sourceReference + '/' + path) : uri.file(path); this.available = available; } diff --git a/src/vs/workbench/parts/debug/common/replHistory.ts b/src/vs/workbench/parts/debug/common/replHistory.ts index 9ed159fe91b..92beb99600d 100644 --- a/src/vs/workbench/parts/debug/common/replHistory.ts +++ b/src/vs/workbench/parts/debug/common/replHistory.ts @@ -17,12 +17,12 @@ export class ReplHistory { private historyPointer: number; private currentExpressionStoredMarkers: boolean; - private historyOverwrites: { [position: string]: string; }; + private historyOverwrites: Map; constructor(private history: string[]) { this.historyPointer = this.history.length; this.currentExpressionStoredMarkers = false; - this.historyOverwrites = {}; + this.historyOverwrites = new Map(); } public next(): string { @@ -48,8 +48,8 @@ export class ReplHistory { this.historyPointer = newPointer; // check for overwrite - if (this.historyOverwrites && this.historyOverwrites[newPointer.toString()]) { - return this.historyOverwrites[newPointer.toString()]; + if (this.historyOverwrites.has(newPointer.toString())) { + return this.historyOverwrites.get(newPointer.toString()); } return this.history[newPointer]; @@ -78,11 +78,7 @@ export class ReplHistory { // keep edits that are made to history items up until the user actually evaluates a expression else { - if (!this.historyOverwrites) { - this.historyOverwrites = {}; - } - - this.historyOverwrites[previousPointer.toString()] = expression; + this.historyOverwrites.set(previousPointer.toString(), expression); } } @@ -104,7 +100,7 @@ export class ReplHistory { this.currentExpressionStoredMarkers = false; // reset overwrites - this.historyOverwrites = null; + this.historyOverwrites.clear(); } public save(): string[] { diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts index c04bab73036..828364cbc40 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -133,6 +133,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0), handler(accessor: ServicesAccessor, configurationOrName: any) { const debugService = accessor.get(IDebugService); + if (!configurationOrName) { + const viewModel = debugService.getViewModel(); + if (!viewModel.selectedConfigurationName) { + const name = debugService.getConfigurationManager().getConfigurationNames().shift(); + viewModel.setSelectedConfigurationName(name); + } + configurationOrName = viewModel.selectedConfigurationName; + } + return debugService.createProcess(configurationOrName); }, when: CONTEXT_NOT_IN_DEBUG_MODE, diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 3519e425649..ecec45008b0 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -193,7 +193,7 @@ jsonRegistry.registerSchema(schemaId, schema); export class ConfigurationManager implements debug.IConfigurationManager { private adapters: Adapter[]; - private allModeIdsForBreakpoints: { [key: string]: boolean }; + private breakpointModeIdsSet: Set; constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService, @@ -208,7 +208,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { ) { this.adapters = []; this.registerListeners(); - this.allModeIdsForBreakpoints = {}; + this.breakpointModeIdsSet = new Set(); } private registerListeners(): void { @@ -220,7 +220,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { } if (rawAdapter.enableBreakpointsFor) { rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => { - this.allModeIdsForBreakpoints[modeId] = true; + this.breakpointModeIdsSet.add(modeId); }); } @@ -250,7 +250,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { breakpointsExtPoint.setHandler(extensions => { extensions.forEach(ext => { ext.value.forEach(breakpoints => { - this.allModeIdsForBreakpoints[breakpoints.language] = true; + this.breakpointModeIdsSet.add(breakpoints.language); }); }); }); @@ -386,6 +386,6 @@ export class ConfigurationManager implements debug.IConfigurationManager { const mode = model ? model.getMode() : null; const modeId = mode ? mode.getId() : null; - return !!this.allModeIdsForBreakpoints[modeId]; + return this.breakpointModeIdsSet.has(modeId); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts index 3f866eb3759..4fd7b01fa9d 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts @@ -66,7 +66,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } private getContextMenuActions(breakpoint: IBreakpoint, uri: uri, lineNumber: number): TPromise { - const actions = []; + const actions: IAction[] = []; if (breakpoint) { actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL)); actions.push(this.instantiationService.createInstance(EditConditionalBreakpointAction, EditConditionalBreakpointAction.ID, EditConditionalBreakpointAction.LABEL, this.editor, lineNumber)); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts index 6104389492a..344ef9cf51a 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts @@ -110,13 +110,13 @@ export class DebugHoverWidget implements IContentWidget { } private getExactExpressionRange(lineContent: string, range: Range): Range { - let matchingExpression = undefined; + let matchingExpression: string = undefined; let startOffset = 0; // Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar // Match any character except a set of characters which often break interesting sub-expressions let expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g; - let result = undefined; + let result: RegExpExecArray = undefined; // First find the full expression under the cursor while (result = expression.exec(lineContent)) { @@ -134,7 +134,7 @@ export class DebugHoverWidget implements IContentWidget { // For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated. if (matchingExpression) { let subExpression: RegExp = /\w+/g; - let subExpressionResult = undefined; + let subExpressionResult: RegExpExecArray = undefined; while (subExpressionResult = subExpression.exec(matchingExpression)) { let subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length; if (subEnd >= range.endColumn) { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index ad7f1883698..75c04ff7cde 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -59,7 +59,7 @@ const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname'; export class DebugService implements debug.IDebugService { public _serviceBrand: any; - private sessionStates: { [id: string]: debug.State }; + private sessionStates: Map; private _onDidChangeState: Emitter; private model: Model; private viewModel: ViewModel; @@ -67,9 +67,9 @@ export class DebugService implements debug.IDebugService { private customTelemetryService: ITelemetryService; private lastTaskEvent: TaskEvent; private toDispose: lifecycle.IDisposable[]; - private toDisposeOnSessionEnd: { [id: string]: lifecycle.IDisposable[] }; + private toDisposeOnSessionEnd: Map; private inDebugMode: IContextKey; - private breakpointsToSendOnResourceSaved: { [uri: string]: boolean }; + private breakpointsToSendOnResourceSaved: Set; constructor( @IStorageService private storageService: IStorageService, @@ -93,10 +93,10 @@ export class DebugService implements debug.IDebugService { @IConfigurationService private configurationService: IConfigurationService ) { this.toDispose = []; - this.toDisposeOnSessionEnd = {}; - this.breakpointsToSendOnResourceSaved = {}; + this.toDisposeOnSessionEnd = new Map(); + this.breakpointsToSendOnResourceSaved = new Set(); this._onDidChangeState = new Emitter(); - this.sessionStates = {}; + this.sessionStates = new Map(); this.configurationManager = this.instantiationService.createInstance(ConfigurationManager); this.inDebugMode = debug.CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); @@ -236,8 +236,8 @@ export class DebugService implements debug.IDebugService { } private registerSessionListeners(process: Process, session: RawDebugSession): void { - this.toDisposeOnSessionEnd[session.getId()].push(session); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidInitialize(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session); + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidInitialize(event => { aria.status(nls.localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = () => { if (session && session.configuration.capabilities.supportsConfigurationDoneRequest) { @@ -255,7 +255,7 @@ export class DebugService implements debug.IDebugService { .done(() => this.fetchThreads(session), errors.onUnexpectedError); })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidStop(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidStop(event => { this.setStateAndEmit(session.getId(), debug.State.Stopped); const threadId = event.body.threadId; @@ -283,21 +283,14 @@ export class DebugService implements debug.IDebugService { this.windowService.getWindow().focus(); aria.alert(nls.localize('debuggingPaused', "Debugging paused, reason {0}, {1} {2}", event.body.reason, stackFrameToFocus.source ? stackFrameToFocus.source.name : '', stackFrameToFocus.lineNumber)); - return this.editorService.openEditor({ - resource: stackFrameToFocus.source.uri, - options: { - selection: { startLineNumber: stackFrameToFocus.lineNumber, startColumn: 1 }, - revealIfVisible: true, - revealInCenterIfOutsideViewport: true - } - }); + return stackFrameToFocus.openInEditor(this.editorService); } }); } }, errors.onUnexpectedError); })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidThread(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidThread(event => { if (event.body.reason === 'started') { this.fetchThreads(session).done(undefined, errors.onUnexpectedError); } else if (event.body.reason === 'exited') { @@ -305,7 +298,7 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidTerminateDebugee(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidTerminateDebugee(event => { aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); if (session && session.getId() === event.body.sessionId) { if (event.body && typeof event.body.restart === 'boolean' && event.body.restart) { @@ -316,7 +309,7 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidContinued(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidContinued(event => { const threadId = event.body.allThreadsContinued ? undefined : event.body.threadId; this.model.clearThreads(session.getId(), false, threadId); if (this.viewModel.focusedProcess.getId() === session.getId()) { @@ -325,7 +318,7 @@ export class DebugService implements debug.IDebugService { this.setStateAndEmit(session.getId(), session.requestType === debug.SessionRequestType.LAUNCH_NO_DEBUG ? debug.State.RunningNoDebug : debug.State.Running); })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidOutput(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidOutput(event => { if (!event.body) { return; } @@ -351,7 +344,7 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidBreakpoint(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidBreakpoint(event => { const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined; const breakpoint = this.model.getBreakpoints().filter(bp => bp.idFromAdapter === id).pop(); if (breakpoint) { @@ -364,9 +357,9 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidExitAdapter(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidExitAdapter(event => { // 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905 - if (session && session.configuration.type === 'extensionHost' && this.sessionStates[session.getId()] === debug.State.RunningNoDebug) { + if (session && session.configuration.type === 'extensionHost' && this.sessionStates.get(session.getId()) === debug.State.RunningNoDebug) { this.windowsService.closeExtensionHostWindow(this.contextService.getWorkspace().resource.fsPath); } if (session && session.getId() === event.body.sessionId) { @@ -439,11 +432,11 @@ export class DebugService implements debug.IDebugService { const focusedProcess = this.viewModel.focusedProcess; if (focusedProcess) { - return this.sessionStates[focusedProcess.getId()]; + return this.sessionStates.get(focusedProcess.getId()); } const processes = this.model.getProcesses(); if (processes.length > 0) { - return this.sessionStates[processes[0].getId()]; + return this.sessionStates.get(processes[0].getId()); } return debug.State.Inactive; @@ -454,7 +447,7 @@ export class DebugService implements debug.IDebugService { } private setStateAndEmit(sessionId: string, newState: debug.State): void { - this.sessionStates[sessionId] = newState; + this.sessionStates.set(sessionId, newState); this._onDidChangeState.fire(); } @@ -671,9 +664,9 @@ export class DebugService implements debug.IDebugService { if (!this.viewModel.focusedProcess) { this.focusStackFrameAndEvaluate(null, process); } - this.toDisposeOnSessionEnd[session.getId()] = []; + this.toDisposeOnSessionEnd.set(session.getId(), []); if (client) { - this.toDisposeOnSessionEnd[session.getId()].push(client); + this.toDisposeOnSessionEnd.get(session.getId()).push(client); } this.registerSessionListeners(process, session); @@ -846,7 +839,7 @@ export class DebugService implements debug.IDebugService { }); try { - this.toDisposeOnSessionEnd[session.getId()] = lifecycle.dispose(this.toDisposeOnSessionEnd[session.getId()]); + this.toDisposeOnSessionEnd.set(session.getId(), lifecycle.dispose(this.toDisposeOnSessionEnd.get(session.getId()))); } catch (e) { // an internal module might be open so the dispose can throw -> ignore and continue with stop session. } @@ -903,7 +896,7 @@ export class DebugService implements debug.IDebugService { } if (this.textFileService.isDirty(modelUri)) { // Only send breakpoints for a file once it is not dirty #8077 - this.breakpointsToSendOnResourceSaved[modelUri.toString()] = true; + this.breakpointsToSendOnResourceSaved.add(modelUri.toString()); return TPromise.as(null); } @@ -998,8 +991,8 @@ export class DebugService implements debug.IDebugService { fileChangesEvent.contains(bp.uri, FileChangeType.DELETED))); fileChangesEvent.getUpdated().forEach(event => { - if (this.breakpointsToSendOnResourceSaved[event.resource.toString()]) { - this.breakpointsToSendOnResourceSaved[event.resource.toString()] = false; + if (this.breakpointsToSendOnResourceSaved.has(event.resource.toString())) { + this.breakpointsToSendOnResourceSaved.delete(event.resource.toString()); this.sendBreakpoints(event.resource, true).done(null, errors.onUnexpectedError); } }); @@ -1015,7 +1008,7 @@ export class DebugService implements debug.IDebugService { } public dispose(): void { - Object.keys(this.toDisposeOnSessionEnd).forEach(key => lifecycle.dispose(this.toDisposeOnSessionEnd[key])); + this.toDisposeOnSessionEnd.forEach(toDispose => lifecycle.dispose(toDispose)); this.toDispose = lifecycle.dispose(this.toDispose); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts index c8e9b640f37..d82164b1452 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts @@ -309,15 +309,7 @@ export class CallStackController extends BaseDebugController { private focusStackFrame(stackFrame: debug.IStackFrame, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void { this.debugService.focusStackFrameAndEvaluate(stackFrame).then(() => { const sideBySide = (event && (event.ctrlKey || event.metaKey)); - return this.editorService.openEditor({ - resource: stackFrame.source.uri, - options: { - preserveFocus, - selection: { startLineNumber: stackFrame.lineNumber, startColumn: 1 }, - revealIfVisible: true, - revealInCenterIfOutsideViewport: true - }, - }, sideBySide); + return stackFrame.openInEditor(this.editorService, preserveFocus, sideBySide); }, errors.onUnexpectedError); } } @@ -563,7 +555,7 @@ export class CallStackRenderer implements IRenderer { private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void { stackFrame.source.available ? dom.removeClass(data.stackFrame, 'disabled') : dom.addClass(data.stackFrame, 'disabled'); - data.file.title = stackFrame.source.uri.fsPath; + data.file.title = stackFrame.source.raw.path || stackFrame.source.name; data.label.textContent = stackFrame.name; data.label.title = stackFrame.name; data.fileName.textContent = getSourceName(stackFrame.source, this.contextService); diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 7b5e93d976f..64ecd31fb0b 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -29,7 +29,6 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ReplExpressionsRenderer, ReplExpressionsController, ReplExpressionsDataSource, ReplExpressionsActionProvider, ReplExpressionsAccessibilityProvider } from 'vs/workbench/parts/debug/electron-browser/replViewer'; @@ -81,7 +80,6 @@ export class Repl extends Panel implements IPrivateReplService { constructor( @debug.IDebugService private debugService: debug.IDebugService, - @IContextMenuService private contextMenuService: IContextMenuService, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService, @IStorageService private storageService: IStorageService, diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index 22b9551cb7c..ed9ad8d3cfd 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -373,7 +373,7 @@ export class ReplExpressionsRenderer implements IRenderer { pattern.lastIndex = 0; // the holy grail of software development const match = pattern.exec(text); - let resource = null; + let resource: uri = null; try { resource = match && uri.file(match[1]); } catch (e) { } diff --git a/src/vs/workbench/parts/debug/node/v8Protocol.ts b/src/vs/workbench/parts/debug/node/v8Protocol.ts index 3dd75e0e8db..8c368528db6 100644 --- a/src/vs/workbench/parts/debug/node/v8Protocol.ts +++ b/src/vs/workbench/parts/debug/node/v8Protocol.ts @@ -13,14 +13,14 @@ export abstract class V8Protocol { private outputStream: stream.Writable; private sequence: number; - private pendingRequests: { [id: number]: (e: DebugProtocol.Response) => void; }; + private pendingRequests: Map void>; private rawData: Buffer; private contentLength: number; constructor(private id: string) { this.sequence = 1; this.contentLength = -1; - this.pendingRequests = {}; + this.pendingRequests = new Map void>(); this.rawData = new Buffer(0); } @@ -77,7 +77,7 @@ export abstract class V8Protocol { if (clb) { // store callback for this request - this.pendingRequests[request.seq] = clb; + this.pendingRequests.set(request.seq, clb); } } @@ -130,9 +130,9 @@ export abstract class V8Protocol { break; case 'response': const response = rawData; - const clb = this.pendingRequests[response.request_seq]; + const clb = this.pendingRequests.get(response.request_seq); if (clb) { - delete this.pendingRequests[response.request_seq]; + this.pendingRequests.delete(response.request_seq); clb(response); } break; diff --git a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts b/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts index 183d1ddccd9..28ed164473a 100644 --- a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts +++ b/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts @@ -50,19 +50,11 @@ export class TreeExplorerView extends CollapsibleViewletView { const dataSource = this.instantiationService.createInstance(TreeDataSource, this.treeNodeProviderId); const renderer = this.instantiationService.createInstance(TreeRenderer, this.viewletState, this.actionRunner, container.getHTMLElement()); const controller = this.instantiationService.createInstance(TreeController, this.treeNodeProviderId); - const sorter = null; - const filter = null; - const dnd = null; - const accessibilityProvider = null; return new Tree(container.getHTMLElement(), { dataSource, renderer, - controller, - sorter, - filter, - dnd, - accessibilityProvider + controller }); } diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index 524f44128e4..eba4ecb0047 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -313,7 +313,7 @@ export class DropDownMenuActionItem extends ActionItem { } private getActions(): IAction[] { - let actions = []; + let actions: IAction[] = []; const menuActionGroups = this.menuActionGroups.filter(group => group.some(action => action.enabled)); for (const menuActions of menuActionGroups) { actions = [...actions, ...menuActions, new Separator()]; diff --git a/src/vs/workbench/parts/extensions/browser/extensionsList.ts b/src/vs/workbench/parts/extensions/browser/extensionsList.ts index c5dd01e143a..88bb1de5ce3 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsList.ts @@ -23,6 +23,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IExtensionService } from 'vs/platform/extensions/common/extensions'; export interface ITemplateData { + root: HTMLElement; element: HTMLElement; icon: HTMLImageElement; name: HTMLElement; @@ -92,7 +93,7 @@ export class Renderer implements IPagedRenderer { const disposables = [versionWidget, installCountWidget, ratingsWidget, builtinStatusAction, updateAction, reloadAction, manageAction, actionbar]; return { - element, icon, name, installCount, ratings, author, description, disposables, + root, element, icon, name, installCount, ratings, author, description, disposables, extensionDisposables: [], set extension(extension: IExtension) { versionWidget.extension = extension; @@ -110,6 +111,7 @@ export class Renderer implements IPagedRenderer { renderPlaceholder(index: number, data: ITemplateData): void { addClass(data.element, 'loading'); + data.root.removeAttribute('aria-label'); data.extensionDisposables = dispose(data.extensionDisposables); data.icon.src = ''; data.name.textContent = ''; @@ -142,6 +144,7 @@ export class Renderer implements IPagedRenderer { data.icon.style.visibility = 'inherit'; } + data.root.setAttribute('aria-label', extension.displayName); data.name.textContent = extension.displayName; data.author.textContent = extension.publisherDisplayName; data.description.textContent = extension.description; diff --git a/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts b/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts index db0612bd1f8..7f99ba447e9 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts @@ -72,7 +72,7 @@ export class GalleryExtensionsHandler extends QuickOpenHandler { } getResults(text: string): TPromise> { - const entries = []; + const entries: SimpleEntry[] = []; if (text) { const label = nls.localize('searchFor', "Press Enter to search for '{0}' in the Marketplace.", text); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 08263b5fe23..6ee74bb6e7a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -102,7 +102,9 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { const delegate = new Delegate(); const renderer = this.instantiationService.createInstance(Renderer); - this.list = new PagedList(this.extensionsBox, delegate, [renderer]); + this.list = new PagedList(this.extensionsBox, delegate, [renderer], { + ariaLabel: localize('extensions', "Extensions") + }); const onKeyDown = chain(domEvent(this.searchBox, 'keydown')) .map(e => new StandardKeyboardEvent(e)); @@ -434,7 +436,7 @@ export class StatusUpdater implements IWorkbenchContribution { private onServiceChange(): void { if (this.extensionsWorkbenchService.local.some(e => e.state === ExtensionState.Installing)) { - this.activityBarService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', 'Extensions')), 'extensions-badge progress-badge'); + this.activityBarService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', "Extensions")), 'extensions-badge progress-badge'); return; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css index a0d329f9f8f..8acfffe0567 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css @@ -3,157 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.quick-open-widget .extension { - padding: 0 14px 0 0; - height: 48px; -} - -.quick-open-widget .extension.loading, -.extensions-viewlet > .extensions .extension.loading, -.extension-editor > .body > .content.loading { - background: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjU3NSIgaGVpZ2h0PSI2cHgiPg0KICA8c3R5bGU+DQogICAgY2lyY2xlIHsNCiAgICAgIGFuaW1hdGlvbjogYmFsbCAyLjVzIGN1YmljLWJlemllcigwLjAwMCwgMS4wMDAsIDEuMDAwLCAwLjAwMCkgaW5maW5pdGU7DQogICAgICBmaWxsOiAjYmJiOw0KICAgIH0NCg0KICAgICNiYWxscyB7DQogICAgICBhbmltYXRpb246IGJhbGxzIDIuNXMgbGluZWFyIGluZmluaXRlOw0KICAgIH0NCg0KICAgICNjaXJjbGUyIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjFzOyB9DQogICAgI2NpcmNsZTMgeyBhbmltYXRpb24tZGVsYXk6IDAuMnM7IH0NCiAgICAjY2lyY2xlNCB7IGFuaW1hdGlvbi1kZWxheTogMC4zczsgfQ0KICAgICNjaXJjbGU1IHsgYW5pbWF0aW9uLWRlbGF5OiAwLjRzOyB9DQoNCiAgICBAa2V5ZnJhbWVzIGJhbGwgew0KICAgICAgZnJvbSB7IHRyYW5zZm9ybTogbm9uZTsgfQ0KICAgICAgMjAlIHsgdHJhbnNmb3JtOiBub25lOyB9DQogICAgICA4MCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoODY0cHgpOyB9DQogICAgICB0byB7IHRyYW5zZm9ybTogdHJhbnNsYXRlWCg4NjRweCk7IH0NCiAgICB9DQoNCiAgICBAa2V5ZnJhbWVzIGJhbGxzIHsNCiAgICAgIGZyb20geyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTQwcHgpOyB9DQogICAgICB0byB7IHRyYW5zZm9ybTogdHJhbnNsYXRlWCgzMHB4KTsgfQ0KICAgIH0NCiAgPC9zdHlsZT4NCiAgPGcgaWQ9ImJhbGxzIj4NCiAgICA8Y2lyY2xlIGNsYXNzPSJjaXJjbGUiIGlkPSJjaXJjbGUxIiBjeD0iLTExNSIgY3k9IjMiIHI9IjMiLz4NCiAgICA8Y2lyY2xlIGNsYXNzPSJjaXJjbGUiIGlkPSJjaXJjbGUyIiBjeD0iLTEzMCIgY3k9IjMiIHI9IjMiIC8+DQogICAgPGNpcmNsZSBjbGFzcz0iY2lyY2xlIiBpZD0iY2lyY2xlMyIgY3g9Ii0xNDUiIGN5PSIzIiByPSIzIiAvPg0KICAgIDxjaXJjbGUgY2xhc3M9ImNpcmNsZSIgaWQ9ImNpcmNsZTQiIGN4PSItMTYwIiBjeT0iMyIgcj0iMyIgLz4NCiAgICA8Y2lyY2xlIGNsYXNzPSJjaXJjbGUiIGlkPSJjaXJjbGU1IiBjeD0iLTE3NSIgY3k9IjMiIHI9IjMiIC8+DQogIDwvZz4NCjwvc3ZnPg==') center center no-repeat; -} - -.quick-open-widget .extension.loading > * { - opacity: 0.4; -} - -.quick-open-widget .extension .row { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - height: 24px; -} - -.quick-open-widget .extension .row .actions { - float: right; -} - -.quick-open-widget .extension .description { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - opacity: 0.6; -} - -.quick-open-widget .extension .install { - margin-left: 6px; - padding: 1px 3px; - border-radius: 3px; - background-color: rgba(132, 132, 132, 0.3); - font-size: smaller; - opacity: 0.7; -} - -.quick-open-widget .extension .install > .octicon { - font-size: small; - margin-right: 3px; -} - -.quick-open-widget .extension .icon.octicon-x:before { - margin-right: 2px; - margin-top: 2px; - display: inline-block; -} - -.quick-open-widget .extension .published { - float: right; - opacity: 0.6; - font-size: smaller; -} - -.quick-open-widget .extension .published > .version { - opacity: 0.6; - margin-right: 0.5em; -} - -@keyframes move-background { - to { background-position: 8px 0; } -} - -.quick-open-widget .extension .actions .action-item:not(.disabled) { - display: none; -} - -.quick-open-widget .monaco-tree-row:hover .extension .actions .action-item, -.quick-open-widget .monaco-tree-row.focused .extension .actions .action-item { - display: inherit; -} - -.quick-open-widget .extension .actions .action-item:not(:first-child) { - margin-left: 2px; -} - -.quick-open-widget .extension .actions .action-item { - line-height: 12px; -} - -.quick-open-widget .extension .actions .action-label { - width: 12px; - height: 12px; - font-size: smaller; - border: 1px solid rgba(132, 132, 132, 0.5); - /*border-radius: 2px;*/ - padding: 1px; - vertical-align: text-bottom; - text-transform: uppercase; - color: rgb(0, 157, 255); - border-color: rgb(0, 157, 255); -} - -.quick-open-widget .monaco-tree.focused .monaco-tree-row.focused .extension .actions .action-label:focus { - outline: 1px solid #DF740C; - outline-offset: -1px; -} - -.quick-open-widget .monaco-tree.focused .monaco-tree-row.focused .extension .actions .action-label:active { - outline: none; -} - -.quick-open-widget .extension .actions .action-label:not(.icon) { - padding: 1px 3px; - display: inline-block; - width: auto; - font-size: x-small; -} - -.quick-open-widget .extension .actions .action-item.disabled .action-label { - animation: move-background 0.5s linear infinite; - background-color: rgba(132, 132, 132, 0.5); - background-size: 8px; - background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0.5) 75%, transparent 75%, transparent); - color: rgb(0, 0, 0); - border-color: rgb(0, 0, 0); -} - -.quick-open-widget .extension .actions .action-item .action-label:hover { - text-decoration: none; -} - -.quick-open-widget .extension .actions .action-item:not(.disabled) .action-label:hover { - color: inherit; - background: rgba(132, 132, 132, 0.2); - border-color: #CCC; -} - -.quick-open-widget .extension .actions .action-item:not(.disabled) .action-label:active { - background: rgba(132, 132, 132, 0.5); -} - -.quick-open-widget .extension .actions .action-item:active { - transform: none; -} - -.monaco-workbench > .activitybar .monaco-action-bar .action-label.extensions { - background: url('extensions-status.svg') center center no-repeat; -} - -/* Global action */ - -.monaco-workbench > .activitybar .monaco-action-bar .action-label.extensions { - background-size: 22px; - background-repeat: no-repeat; - background-position: 50% !important; +.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label.extensions { + background: url('extensions-status.svg') center center/22px no-repeat; } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 2cda12aa0b4..64e21cd4d8d 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -580,11 +580,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { .then(null, onUnexpectedError); } } - } - - if (extension.gallery) { - // Report telemetry only for gallery extensions - this.reportTelemetry(installing, !error); + if (extension.gallery) { + // Report telemetry only for gallery extensions + this.reportTelemetry(installing, !error); + } } this._onChange.fire(); } diff --git a/src/vs/workbench/parts/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/parts/files/browser/editors/binaryFileEditor.ts index a8d93d2d011..1da3b242a48 100644 --- a/src/vs/workbench/parts/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/parts/files/browser/editors/binaryFileEditor.ts @@ -23,6 +23,6 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { } public getTitle(): string { - return this.getInput() ? this.getInput().getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); + return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts index 0141bb01fe9..38f317eaaea 100644 --- a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts @@ -27,7 +27,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMessageService, CancelAction } from 'vs/platform/message/common/message'; +import { CancelAction } from 'vs/platform/message/common/message'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; @@ -44,17 +44,16 @@ export class TextFileEditor extends BaseTextEditor { @IFileService private fileService: IFileService, @IViewletService private viewletService: IViewletService, @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService contextService: IWorkspaceContextService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, @IHistoryService private historyService: IHistoryService, - @IMessageService messageService: IMessageService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, @IEditorGroupService private editorGroupService: IEditorGroupService, @ITextFileService textFileService: ITextFileService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, contextService, storageService, messageService, configurationService, editorService, themeService, textFileService); + super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService); // Clear view state for deleted files this.toUnbind.push(this.fileService.onFileChanges(e => this.onFilesChanged(e))); @@ -68,15 +67,15 @@ export class TextFileEditor extends BaseTextEditor { } public getTitle(): string { - return this.getInput() ? this.getInput().getName() : nls.localize('textFileEditor', "Text File Editor"); + return this.input ? this.input.getName() : nls.localize('textFileEditor', "Text File Editor"); } - public getInput(): FileEditorInput { - return super.getInput(); + public get input(): FileEditorInput { + return this._input as FileEditorInput; } public setInput(input: FileEditorInput, options?: EditorOptions): TPromise { - const oldInput = this.getInput(); + const oldInput = this.input; super.setInput(input, options); // Detect options @@ -121,9 +120,9 @@ export class TextFileEditor extends BaseTextEditor { // Check Model state const textFileModel = resolvedModel; - const hasInput = !!this.getInput(); + const hasInput = !!this.input; const modelDisposed = textFileModel.isDisposed(); - const inputChanged = hasInput && this.getInput().getResource().toString() !== textFileModel.getResource().toString(); + const inputChanged = hasInput && this.input.getResource().toString() !== textFileModel.getResource().toString(); if ( !hasInput || // editor got hidden meanwhile modelDisposed || // input got disposed meanwhile @@ -137,7 +136,7 @@ export class TextFileEditor extends BaseTextEditor { textEditor.setModel(textFileModel.textEditorModel); // Always restore View State if any associated - const editorViewState = this.loadTextEditorViewState(this.getInput().getResource().toString()); + const editorViewState = this.loadTextEditorViewState(this.input.getResource().toString()); if (editorViewState) { textEditor.restoreViewState(editorViewState); } @@ -210,7 +209,7 @@ export class TextFileEditor extends BaseTextEditor { protected getCodeEditorOptions(): IEditorOptions { const options = super.getCodeEditorOptions(); - const input = this.getInput(); + const input = this.input; const inputName = input && input.getName(); let ariaLabel: string; @@ -237,7 +236,7 @@ export class TextFileEditor extends BaseTextEditor { // Keep editor view state in settings to restore when coming back if (this.input) { - this.saveTextEditorViewState(this.getInput().getResource().toString()); + this.saveTextEditorViewState(this.input.getResource().toString()); } // Clear Model @@ -251,7 +250,7 @@ export class TextFileEditor extends BaseTextEditor { // Save View State if (this.input) { - this.saveTextEditorViewState(this.getInput().getResource().toString()); + this.saveTextEditorViewState(this.input.getResource().toString()); } // Call Super diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 53ac3369199..623df36d1cb 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -44,8 +44,8 @@ import { IInstantiationService, IConstructorSignature2 } from 'vs/platform/insta import { IMessageService, IMessageWithAction, IConfirmation, Severity, CancelAction } from 'vs/platform/message/common/message'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Keybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { Selection } from 'vs/editor/common/core/selection'; import { getCodeEditor } from 'vs/editor/common/services/codeEditorService'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; export interface IEditableData { action: IAction; @@ -65,8 +65,6 @@ export class BaseFileAction extends Action { constructor( id: string, label: string, - @IWorkspaceContextService private _contextService: IWorkspaceContextService, - @IWorkbenchEditorService private _editorService: IWorkbenchEditorService, @IFileService private _fileService: IFileService, @IMessageService private _messageService: IMessageService, @ITextFileService private _textFileService: ITextFileService @@ -76,18 +74,10 @@ export class BaseFileAction extends Action { this.enabled = false; } - public get contextService() { - return this._contextService; - } - public get messageService() { return this._messageService; } - public get editorService() { - return this._editorService; - } - public get fileService() { return this._fileService; } @@ -109,7 +99,7 @@ export class BaseFileAction extends Action { } _updateEnablement(): void { - this.enabled = !!(this._contextService && this._fileService && this._editorService && this._isEnabled()); + this.enabled = !!(this._fileService && this._isEnabled()); } protected onError(error: any): void { @@ -149,14 +139,12 @@ export class TriggerRenameFileAction extends BaseFileAction { constructor( tree: ITree, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService, @IInstantiationService instantiationService: IInstantiationService ) { - super(TriggerRenameFileAction.ID, nls.localize('rename', "Rename"), contextService, editorService, fileService, messageService, textFileService); + super(TriggerRenameFileAction.ID, nls.localize('rename', "Rename"), fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -220,13 +208,11 @@ export abstract class BaseRenameAction extends BaseFileAction { id: string, label: string, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(id, label, contextService, editorService, fileService, messageService, textFileService); + super(id, label, fileService, messageService, textFileService); this.element = element; } @@ -283,13 +269,11 @@ class RenameFileAction extends BaseRenameAction { constructor( element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(RenameFileAction.ID, nls.localize('rename', "Rename"), element, contextService, editorService, fileService, messageService, textFileService); + super(RenameFileAction.ID, nls.localize('rename', "Rename"), element, fileService, messageService, textFileService); this._updateEnablement(); } @@ -347,13 +331,11 @@ export class BaseNewAction extends BaseFileAction { isFile: boolean, editableAction: BaseRenameAction, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(id, label, contextService, editorService, fileService, messageService, textFileService); + super(id, label, fileService, messageService, textFileService); if (element) { this.presetFolder = element.isDirectory ? element : element.parent; @@ -437,14 +419,12 @@ export class NewFileAction extends BaseNewAction { constructor( tree: ITree, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService, @IInstantiationService instantiationService: IInstantiationService ) { - super('workbench.action.files.newFile', nls.localize('newFile', "New File"), tree, true, instantiationService.createInstance(CreateFileAction, element), null, contextService, editorService, fileService, messageService, textFileService); + super('workbench.action.files.newFile', nls.localize('newFile', "New File"), tree, true, instantiationService.createInstance(CreateFileAction, element), null, fileService, messageService, textFileService); this.class = 'explorer-action new-file'; this._updateEnablement(); @@ -457,14 +437,12 @@ export class NewFolderAction extends BaseNewAction { constructor( tree: ITree, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService, @IInstantiationService instantiationService: IInstantiationService ) { - super('workbench.action.files.newFolder', nls.localize('newFolder', "New Folder"), tree, false, instantiationService.createInstance(CreateFolderAction, element), null, contextService, editorService, fileService, messageService, textFileService); + super('workbench.action.files.newFolder', nls.localize('newFolder', "New Folder"), tree, false, instantiationService.createInstance(CreateFolderAction, element), null, fileService, messageService, textFileService); this.class = 'explorer-action new-folder'; this._updateEnablement(); @@ -581,13 +559,11 @@ export class CreateFileAction extends BaseCreateAction { constructor( element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(CreateFileAction.ID, CreateFileAction.LABEL, element, contextService, editorService, fileService, messageService, textFileService); + super(CreateFileAction.ID, CreateFileAction.LABEL, element, fileService, messageService, textFileService); this._updateEnablement(); } @@ -607,13 +583,11 @@ export class CreateFolderAction extends BaseCreateAction { constructor( element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(CreateFolderAction.ID, CreateFolderAction.LABEL, null, contextService, editorService, fileService, messageService, textFileService); + super(CreateFolderAction.ID, CreateFolderAction.LABEL, null, fileService, messageService, textFileService); this._updateEnablement(); } @@ -636,13 +610,11 @@ export class BaseDeleteFileAction extends BaseFileAction { tree: ITree, element: FileStat, useTrash: boolean, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(id, label, contextService, editorService, fileService, messageService, textFileService); + super(id, label, fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -759,13 +731,11 @@ export class MoveFileToTrashAction extends BaseDeleteFileAction { constructor( tree: ITree, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(MoveFileToTrashAction.ID, nls.localize('delete', "Delete"), tree, element, true, contextService, editorService, fileService, messageService, textFileService); + super(MoveFileToTrashAction.ID, nls.localize('delete', "Delete"), tree, element, true, fileService, messageService, textFileService); } } @@ -779,13 +749,11 @@ export class ImportFileAction extends BaseFileAction { tree: ITree, element: FileStat, clazz: string, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(ImportFileAction.ID, nls.localize('importFiles', "Import Files"), contextService, editorService, fileService, messageService, textFileService); + super(ImportFileAction.ID, nls.localize('importFiles', "Import Files"), fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -887,13 +855,11 @@ export class CopyFileAction extends BaseFileAction { constructor( tree: ITree, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(CopyFileAction.ID, nls.localize('copyFile', "Copy"), contextService, editorService, fileService, messageService, textFileService); + super(CopyFileAction.ID, nls.localize('copyFile', "Copy"), fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -926,14 +892,12 @@ export class PasteFileAction extends BaseFileAction { constructor( tree: ITree, element: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService, @IInstantiationService private instantiationService: IInstantiationService ) { - super(PasteFileAction.ID, nls.localize('pasteFile', "Paste"), contextService, editorService, fileService, messageService, textFileService); + super(PasteFileAction.ID, nls.localize('pasteFile', "Paste"), fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -991,13 +955,11 @@ export class DuplicateFileAction extends BaseFileAction { tree: ITree, element: FileStat, target: FileStat, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super('workbench.files.action.duplicateFile', nls.localize('duplicateFile', "Duplicate"), contextService, editorService, fileService, messageService, textFileService); + super('workbench.files.action.duplicateFile', nls.localize('duplicateFile', "Duplicate"), fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -1157,11 +1119,11 @@ export class GlobalCompareResourcesAction extends Action { } public run(): TPromise { - const fileResource = toResource(this.editorService.getActiveEditorInput(), { filter: 'file' }); - if (fileResource) { + const activeResource = toResource(this.editorService.getActiveEditorInput(), { filter: ['file', 'untitled'] }); + if (activeResource) { // Keep as resource to compare - globalResourceToCompare = fileResource; + globalResourceToCompare = activeResource; // Pick another entry from history interface IHistoryPickEntry extends IFilePickOpenEntry { @@ -1175,18 +1137,22 @@ export class GlobalCompareResourcesAction extends Action { let description: string; if (input instanceof EditorInput) { - return void 0; // only files supported + resource = toResource(input, { filter: ['file', 'untitled'] }); + } else { + resource = (input as IResourceInput).resource; } - const resourceInput = input as IResourceInput; - resource = resourceInput.resource; - label = paths.basename(resourceInput.resource.fsPath); - description = labels.getPathLabel(paths.dirname(resource.fsPath), this.contextService); + if (!resource) { + return void 0; // only support to compare with files and untitled + } + + label = paths.basename(resource.fsPath); + description = resource.scheme === 'file' ? labels.getPathLabel(paths.dirname(resource.fsPath), this.contextService) : void 0; return { input, resource, label, description }; }).filter(p => !!p); - return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickHistory', "Select an editor history entry to compare with"), autoFocus: { autoFocusFirstEntry: true }, matchOnDescription: true }).then(pick => { + return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickHistory', "Select a previously opened file to compare with"), autoFocus: { autoFocusFirstEntry: true }, matchOnDescription: true }).then(pick => { if (pick) { const compareAction = this.instantiationService.createInstance(CompareResourcesAction, pick.resource, null); if (compareAction._isEnabled()) { @@ -1212,7 +1178,6 @@ export class CompareResourcesAction extends Action { constructor( resource: URI, tree: ITree, - @IInstantiationService private instantiationService: IInstantiationService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService ) { super('workbench.files.action.compareFiles', CompareResourcesAction.computeLabel()); @@ -1342,13 +1307,13 @@ export abstract class BaseSaveFileAction extends BaseActionWithErrorReporting { encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! } - let selectionOfSource: Selection; + let viewStateOfSource: IEditorViewState; const activeEditor = this.editorService.getActiveEditor(); const editor = getCodeEditor(activeEditor); if (editor) { const activeResource = toResource(activeEditor.input, { supportSideBySide: true, filter: ['file', 'untitled'] }); if (activeResource && activeResource.toString() === source.toString()) { - selectionOfSource = editor.getSelection(); + viewStateOfSource = editor.saveViewState(); } } @@ -1379,13 +1344,13 @@ export abstract class BaseSaveFileAction extends BaseActionWithErrorReporting { encoding: encodingOfSource, options: { pinned: true, - selection: selectionOfSource + viewState: viewStateOfSource } }; return this.editorService.replaceEditors([{ toReplace: { resource: source }, - replaceWith: replaceWith + replaceWith }]).then(() => true); }); } diff --git a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts index 8de0dfa8fc3..1e716709f59 100644 --- a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts @@ -183,7 +183,6 @@ class ResolveSaveConflictMessage implements IMessageWithAction { model: ITextFileEditorModel, message: string, @IMessageService private messageService: IMessageService, - @IInstantiationService private instantiationService: IInstantiationService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEnvironmentService private environmentService: IEnvironmentService ) { diff --git a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts index 7b9579c7f95..99b26caf5ef 100644 --- a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts @@ -413,6 +413,7 @@ export class ActionProvider extends ContributableActionProvider { result.unshift(this.instantiationService.createInstance(OpenToSideAction, tree, resource, false)); if (!openEditor.isUntitled()) { + // Files: Save / Revert if (!autoSaveEnabled) { result.push(new Separator()); @@ -427,18 +428,10 @@ export class ActionProvider extends ContributableActionProvider { revertAction.enabled = openEditor.isDirty(); result.push(revertAction); } - - result.push(new Separator()); - - // Compare Actions - const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, resource, tree); - if (runCompareAction._isEnabled()) { - result.push(runCompareAction); - } - result.push(this.instantiationService.createInstance(SelectResourceForCompareAction, resource, tree)); } + // Untitled: Save / Save As - else { + if (openEditor.isUntitled()) { result.push(new Separator()); if (this.untitledEditorService.hasAssociatedFilePath(resource)) { @@ -452,6 +445,14 @@ export class ActionProvider extends ContributableActionProvider { result.push(saveAsAction); } + // Compare Actions + result.push(new Separator()); + const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, resource, tree); + if (runCompareAction._isEnabled()) { + result.push(runCompareAction); + } + result.push(this.instantiationService.createInstance(SelectResourceForCompareAction, resource, tree)); + result.push(new Separator()); } diff --git a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts index 07a8345e710..a22a107ba2b 100644 --- a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts @@ -132,12 +132,7 @@ export class OpenFileAction extends Action { run(): TPromise { const fileResource = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); - // Handle in browser process - if (fileResource) { - return this.windowService.openFilePicker(false, paths.dirname(fileResource.fsPath)); - } - - return this.windowService.openFilePicker(); + return this.windowService.openFilePicker(false, fileResource ? paths.dirname(fileResource.fsPath) : void 0); } } @@ -159,7 +154,7 @@ export class ShowOpenedFileInNewWindow extends Action { public run(): TPromise { const fileResource = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); if (fileResource) { - this.windowsService.windowOpen([fileResource.fsPath], true); + this.windowsService.openWindow([fileResource.fsPath], { forceNewWindow: true }); } else { this.messageService.show(severity.Info, nls.localize('openFileToShow', "Open a file first to open in new window")); } diff --git a/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts b/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts index 2d49be0f862..93bfd9df732 100644 --- a/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts @@ -22,6 +22,9 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { toResource } from 'vs/workbench/common/editor'; +import paths = require('vs/base/common/paths'); +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; class FileViewerActionContributor extends ActionBarContributor { @@ -57,7 +60,7 @@ class FileViewerActionContributor extends ActionBarContributor { // Contribute Actions const category = nls.localize('filesCategory', "Files"); -const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); +const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S }), 'Files: Save As...', category); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'Files: New Untitled File', category); @@ -73,11 +76,11 @@ if (env.isMacintosh) { } // Contribute to File Viewers -const actionsRegistry = Registry.as(ActionBarExtensions.Actionbar); +const actionsRegistry = Registry.as(ActionBarExtensions.Actionbar); actionsRegistry.registerActionBarContributor(Scope.VIEWER, FileViewerActionContributor); // Register Dirty Files Tracker -(Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution( +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( DirtyFilesTracker ); @@ -89,5 +92,14 @@ CommandsRegistry.registerCommand('_files.openFolderPicker', (accessor: ServicesA CommandsRegistry.registerCommand('_files.windowOpen', (accessor: ServicesAccessor, paths: string[], forceNewWindow: boolean) => { const windowsService = accessor.get(IWindowsService); - windowsService.windowOpen(paths, forceNewWindow); + windowsService.openWindow(paths, { forceNewWindow }); +}); + +CommandsRegistry.registerCommand('workbench.action.files.openFileInNewWindow', (accessor: ServicesAccessor) => { + const windowService = accessor.get(IWindowService); + const editorService = accessor.get(IWorkbenchEditorService); + + const fileResource = toResource(editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); + + return windowService.openFilePicker(true, fileResource ? paths.dirname(fileResource.fsPath) : void 0); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/git/electron-browser/gitActions.ts b/src/vs/workbench/parts/git/electron-browser/gitActions.ts index f983b03b2c3..1a809c3fbab 100644 --- a/src/vs/workbench/parts/git/electron-browser/gitActions.ts +++ b/src/vs/workbench/parts/git/electron-browser/gitActions.ts @@ -70,7 +70,7 @@ export class CloneAction extends Action { return clone.then(path => { const forceNewWindow = this.workspaceService.hasWorkspace(); - return this.windowsService.windowOpen([path], forceNewWindow); + return this.windowsService.openWindow([path], { forceNewWindow, forceReuseWindow: !forceNewWindow }); }).then(null, e => { if (/already exists and is not an empty directory/.test(e.stderr || '')) { diff --git a/src/vs/workbench/parts/markers/browser/media/markers.css b/src/vs/workbench/parts/markers/browser/media/markers.css index 491e4ac3590..2cdcded90d2 100644 --- a/src/vs/workbench/parts/markers/browser/media/markers.css +++ b/src/vs/workbench/parts/markers/browser/media/markers.css @@ -46,10 +46,6 @@ line-height: 22px; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry > * { - display: inline-block; -} - .markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-stats { display: inline-block; margin-left: 10px; diff --git a/src/vs/workbench/parts/markers/common/markersModel.ts b/src/vs/workbench/parts/markers/common/markersModel.ts index bd7e75d2a24..ce4524b4ded 100644 --- a/src/vs/workbench/parts/markers/common/markersModel.ts +++ b/src/vs/workbench/parts/markers/common/markersModel.ts @@ -26,8 +26,8 @@ export class Resource { private _path: string = null; constructor(public uri: URI, public markers: Marker[], - public statistics: MarkerStatistics, - public matches: IMatch[] = []) { + public statistics: MarkerStatistics, + public matches: IMatch[] = []) { } public get path(): string { @@ -47,8 +47,8 @@ export class Resource { export class Marker { constructor(public id: string, public marker: IMarker, - public labelMatches: IMatch[] = [], - public sourceMatches: IMatch[] = []) { } + public labelMatches: IMatch[] = [], + public sourceMatches: IMatch[] = []) { } public get resource(): URI { return this.marker.resource; diff --git a/src/vs/workbench/parts/output/browser/output.contribution.ts b/src/vs/workbench/parts/output/browser/output.contribution.ts index c13bc270fc4..4a1e8491e36 100644 --- a/src/vs/workbench/parts/output/browser/output.contribution.ts +++ b/src/vs/workbench/parts/output/browser/output.contribution.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!../browser/media/output.contribution'; +import 'vs/css!./media/output.contribution'; import nls = require('vs/nls'); import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import platform = require('vs/platform/platform'); +import { Registry } from 'vs/platform/platform'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IKeybindings } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -16,7 +16,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { OutputService } from 'vs/workbench/parts/output/browser/outputServices'; import { ToggleOutputAction, ClearOutputAction } from 'vs/workbench/parts/output/browser/outputActions'; import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService } from 'vs/workbench/parts/output/common/output'; -import panel = require('vs/workbench/browser/panel'); +import { PanelRegistry, Extensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { EditorContextKeys } from 'vs/editor/common/editorCommon'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -33,7 +33,7 @@ ModesRegistry.registerLanguage({ }); // Register Output Panel -(platform.Registry.as(panel.Extensions.Panels)).registerPanel(new panel.PanelDescriptor( +Registry.as(Extensions.Panels).registerPanel(new PanelDescriptor( 'vs/workbench/parts/output/browser/outputPanel', 'OutputPanel', OUTPUT_PANEL_ID, @@ -43,7 +43,7 @@ ModesRegistry.registerLanguage({ )); // register toggle output action globally -let actionRegistry = platform.Registry.as(ActionExtensions.WorkbenchActions); +const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, linux: { @@ -64,14 +64,14 @@ interface IActionDescriptor { iconClass?: string; f1?: boolean; - // + // menus menu?: { menuId: MenuId, when?: ContextKeyExpr; group?: string; }; - // + // keybindings keybinding?: { when?: ContextKeyExpr; weight: number; diff --git a/src/vs/workbench/parts/output/browser/outputActions.ts b/src/vs/workbench/parts/output/browser/outputActions.ts index f419657ff6f..94e258c45fe 100644 --- a/src/vs/workbench/parts/output/browser/outputActions.ts +++ b/src/vs/workbench/parts/output/browser/outputActions.ts @@ -90,6 +90,7 @@ export class SwitchOutputActionItem extends SelectActionItem { private static getChannelLabels(outputService: IOutputService): string[] { const contributedChannels = Registry.as(Extensions.OutputChannels).getChannels().map(channelData => channelData.label); + return contributedChannels.sort(); // sort by name } } diff --git a/src/vs/workbench/parts/output/browser/outputEditorInput.ts b/src/vs/workbench/parts/output/browser/outputEditorInput.ts deleted file mode 100644 index 1b9e457be68..00000000000 --- a/src/vs/workbench/parts/output/browser/outputEditorInput.ts +++ /dev/null @@ -1,145 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 nls = require('vs/nls'); -import lifecycle = require('vs/base/common/lifecycle'); -import strings = require('vs/base/common/strings'); -import { TPromise } from 'vs/base/common/winjs.base'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { EditorModel } from 'vs/workbench/common/editor'; -import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput'; -import { OUTPUT_EDITOR_INPUT_ID, OUTPUT_PANEL_ID, IOutputEvent, OUTPUT_MIME, IOutputService, MAX_OUTPUT_LENGTH, IOutputChannel } from 'vs/workbench/parts/output/common/output'; -import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; - -/** - * Output Editor Input - */ -export class OutputEditorInput extends StringEditorInput { - - private static OUTPUT_DELAY = 300; // delay in ms to accumulate output before emitting an event about it - private static instances: { [channel: string]: OutputEditorInput; } = Object.create(null); - - private outputSet: boolean; - private bufferedOutput: string; - private toDispose: lifecycle.IDisposable[]; - private appendOutputScheduler: RunOnceScheduler; - - public static getInstances(): OutputEditorInput[] { - return Object.keys(OutputEditorInput.instances).map((key) => OutputEditorInput.instances[key]); - } - - public static getInstance(instantiationService: IInstantiationService, channel: IOutputChannel): OutputEditorInput { - if (OutputEditorInput.instances[channel.id]) { - return OutputEditorInput.instances[channel.id]; - } - - OutputEditorInput.instances[channel.id] = instantiationService.createInstance(OutputEditorInput, channel); - - return OutputEditorInput.instances[channel.id]; - } - - constructor( - private outputChannel: IOutputChannel, - @IInstantiationService instantiationService: IInstantiationService, - @IOutputService private outputService: IOutputService, - @IPanelService private panelService: IPanelService - ) { - super(nls.localize('output', "Output"), outputChannel ? nls.localize('outputChannel', "for '{0}'", outputChannel.label) : '', '', OUTPUT_MIME, true, instantiationService); - - this.bufferedOutput = ''; - this.toDispose = []; - this.toDispose.push(this.outputService.onOutput(this.onOutputReceived, this)); - this.toDispose.push(this.outputService.onActiveOutputChannel(() => this.scheduleOutputAppend())); - this.toDispose.push(this.panelService.onDidPanelOpen(panel => { - if (panel.getId() === OUTPUT_PANEL_ID) { - this.appendOutput(); - } - })); - - this.appendOutputScheduler = new RunOnceScheduler(() => { - if (this.isVisible()) { - this.appendOutput(); - } - }, OutputEditorInput.OUTPUT_DELAY); - } - - private appendOutput(): void { - if (this.bufferedOutput.length === 0) { - return; - } - - if (this.value.length + this.bufferedOutput.length > MAX_OUTPUT_LENGTH) { - this.setValue(this.outputChannel.output); - } else { - this.append(this.bufferedOutput); - } - this.bufferedOutput = ''; - - const panel = this.panelService.getActivePanel(); - (panel).revealLastLine(true); - } - - private onOutputReceived(e: IOutputEvent): void { - if (this.outputSet && e.channelId === this.outputChannel.id) { - if (e.output) { - this.bufferedOutput = strings.appendWithLimit(this.bufferedOutput, e.output, MAX_OUTPUT_LENGTH); - this.scheduleOutputAppend(); - } else if (e.output === null) { - this.bufferedOutput = ''; - this.clearValue(); // special output indicates we should clear - } - } - } - - private isVisible(): boolean { - const panel = this.panelService.getActivePanel(); - return panel && panel.getId() === OUTPUT_PANEL_ID && this.outputService.getActiveChannel().id === this.outputChannel.id; - } - - private scheduleOutputAppend(): void { - if (this.isVisible() && this.bufferedOutput && !this.appendOutputScheduler.isScheduled()) { - this.appendOutputScheduler.schedule(); - } - } - - public getTypeId(): string { - return OUTPUT_EDITOR_INPUT_ID; - } - - public resolve(refresh?: boolean): TPromise { - return super.resolve(refresh).then(model => { - // Just return model if output already set - if (this.outputSet) { - return model; - } - - this.setValue(this.outputChannel.output); - this.outputSet = true; - - return model; - }); - } - - public matches(otherInput: any): boolean { - if (otherInput instanceof OutputEditorInput) { - let otherOutputEditorInput = otherInput; - if (otherOutputEditorInput.outputChannel.id === this.outputChannel.id) { - return super.matches(otherInput); - } - } - - return false; - } - - public dispose(): void { - this.appendOutputScheduler.dispose(); - this.toDispose = lifecycle.dispose(this.toDispose); - - super.dispose(); - } -} diff --git a/src/vs/workbench/parts/output/browser/outputPanel.ts b/src/vs/workbench/parts/output/browser/outputPanel.ts index 362f0c87133..3967a3dae21 100644 --- a/src/vs/workbench/parts/output/browser/outputPanel.ts +++ b/src/vs/workbench/parts/output/browser/outputPanel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import nls = require('vs/nls'); -import lifecycle = require('vs/base/common/lifecycle'); +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { Action, IAction } from 'vs/base/common/actions'; import { Builder } from 'vs/base/browser/builder'; @@ -14,35 +14,27 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMessageService } from 'vs/platform/message/common/message'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { StringEditor } from 'vs/workbench/browser/parts/editor/stringEditor'; -import { OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/parts/output/common/output'; -import { OutputEditorInput } from 'vs/workbench/parts/output/browser/outputEditorInput'; +import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; +import { OutputEditors, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/parts/output/common/output'; import { SwitchOutputAction, SwitchOutputActionItem, ClearOutputAction } from 'vs/workbench/parts/output/browser/outputActions'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -export class OutputPanel extends StringEditor { - - private toDispose: lifecycle.IDisposable[]; +export class OutputPanel extends TextResourceEditor { + private toDispose: IDisposable[]; private actions: IAction[]; private scopedInstantiationService: IInstantiationService; constructor( @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @IMessageService messageService: IMessageService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, @IOutputService private outputService: IOutputService, @IUntitledEditorService untitledEditorService: IUntitledEditorService, @@ -50,8 +42,8 @@ export class OutputPanel extends StringEditor { @IEditorGroupService editorGroupService: IEditorGroupService, @ITextFileService textFileService: ITextFileService ) { - super(telemetryService, instantiationService, contextService, storageService, - messageService, configurationService, editorService, themeService, untitledEditorService, editorGroupService, textFileService); + super(telemetryService, instantiationService, storageService, configurationService, themeService, untitledEditorService, editorGroupService, textFileService); + this.scopedInstantiationService = instantiationService; this.toDispose = []; } @@ -105,6 +97,7 @@ export class OutputPanel extends StringEditor { } public createEditor(parent: Builder): void { + // First create the scoped instantation service and only then construct the editor using the scoped service const scopedContextKeyService = this.contextKeyService.createScoped(parent.getHTMLElement()); this.toDispose.push(scopedContextKeyService); @@ -112,7 +105,7 @@ export class OutputPanel extends StringEditor { super.createEditor(parent); CONTEXT_IN_OUTPUT.bindTo(scopedContextKeyService).set(true); - this.setInput(OutputEditorInput.getInstance(this.instantiationService, this.outputService.getActiveChannel()), null); + this.setInput(OutputEditors.getInstance(this.instantiationService, this.outputService.getActiveChannel()), null); } public get instantiationService(): IInstantiationService { @@ -120,7 +113,8 @@ export class OutputPanel extends StringEditor { } public dispose(): void { - this.toDispose = lifecycle.dispose(this.toDispose); + this.toDispose = dispose(this.toDispose); + super.dispose(); } } diff --git a/src/vs/workbench/parts/output/browser/outputServices.ts b/src/vs/workbench/parts/output/browser/outputServices.ts index 99bf2e7d57e..cd5d58d457a 100644 --- a/src/vs/workbench/parts/output/browser/outputServices.ts +++ b/src/vs/workbench/parts/output/browser/outputServices.ts @@ -6,22 +6,30 @@ import { TPromise } from 'vs/base/common/winjs.base'; import strings = require('vs/base/common/strings'); import Event, { Emitter } from 'vs/base/common/event'; +import URI from 'vs/base/common/uri'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditor } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/platform'; import { EditorOptions } from 'vs/workbench/common/editor'; -import { IOutputEvent, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, MAX_OUTPUT_LENGTH } from 'vs/workbench/parts/output/common/output'; -import { OutputEditorInput } from 'vs/workbench/parts/output/browser/outputEditorInput'; +import { OutputEditors, IOutputEvent, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, MAX_OUTPUT_LENGTH, OUTPUT_SCHEME, OUTPUT_MIME } from 'vs/workbench/parts/output/common/output'; import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OutputLinkProvider } from 'vs/workbench/parts/output/common/outputLinkProvider'; +import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; +import { IModel } from 'vs/editor/common/editorCommon'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; export class OutputService implements IOutputService { + public _serviceBrand: any; private receivedOutput: { [channel: string]: string; }; @@ -39,7 +47,8 @@ export class OutputService implements IOutputService { @IInstantiationService private instantiationService: IInstantiationService, @IPanelService private panelService: IPanelService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @IModelService modelService: IModelService + @IModelService modelService: IModelService, + @ITextModelResolverService textModelResolverService: ITextModelResolverService ) { this._onOutput = new Emitter(); this._onOutputChannel = new Emitter(); @@ -51,6 +60,9 @@ export class OutputService implements IOutputService { this.activeChannelId = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, channels && channels.length > 0 ? channels[0].id : null); this._outputLinkDetector = new OutputLinkProvider(contextService, modelService); + + // Register as text model content provider for output + textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, instantiationService.createInstance(OutputContentProvider, this)); } public get onOutput(): Event { @@ -126,8 +138,151 @@ export class OutputService implements IOutputService { this._onActiveOutputChannel.fire(channelId); // emit event that a new channel is active return this.panelService.openPanel(OUTPUT_PANEL_ID, !preserveFocus).then((outputPanel: OutputPanel) => { - return outputPanel && outputPanel.setInput(OutputEditorInput.getInstance(this.instantiationService, this.getChannel(channelId)), EditorOptions.create({ preserveFocus: preserveFocus })). + return outputPanel && outputPanel.setInput(OutputEditors.getInstance(this.instantiationService, this.getChannel(channelId)), EditorOptions.create({ preserveFocus: preserveFocus })). then(() => outputPanel); }); } +} + +class OutputContentProvider implements ITextModelContentProvider { + + private static OUTPUT_DELAY = 300; + + private bufferedOutput: { [channel: string]: string; }; + private appendOutputScheduler: { [channel: string]: RunOnceScheduler; }; + + private toDispose: IDisposable[]; + + constructor( + private outputService: IOutputService, + @IModelService private modelService: IModelService, + @IModeService private modeService: IModeService, + @IPanelService private panelService: IPanelService + ) { + this.bufferedOutput = Object.create(null); + this.appendOutputScheduler = Object.create(null); + this.toDispose = []; + + this.registerListeners(); + } + + private registerListeners(): void { + this.toDispose.push(this.outputService.onOutput(e => this.onOutputReceived(e))); + this.toDispose.push(this.outputService.onActiveOutputChannel(channel => this.scheduleOutputAppend(channel))); + this.toDispose.push(this.panelService.onDidPanelOpen(panel => { + if (panel.getId() === OUTPUT_PANEL_ID) { + this.appendOutput(); + } + })); + } + + private onOutputReceived(e: IOutputEvent): void { + const model = this.getModel(e.channelId); + if (!model) { + return; // only react if we have a known model + } + + // Append to model + if (e.output) { + this.bufferedOutput[e.channelId] = strings.appendWithLimit(this.bufferedOutput[e.channelId] || '', e.output, MAX_OUTPUT_LENGTH); + this.scheduleOutputAppend(e.channelId); + } + + // Clear from model + else if (e.output === null) { + this.bufferedOutput[e.channelId] = ''; + model.setValue(''); + } + } + + private getModel(channel: string): IModel { + return this.modelService.getModel(URI.from({ scheme: OUTPUT_SCHEME, path: channel })); + } + + private scheduleOutputAppend(channel: string): void { + if (!this.isVisible(channel)) { + return; // only if the output channel is visible + } + + if (!this.bufferedOutput[channel]) { + return; // only if we have any output to show + } + + let scheduler = this.appendOutputScheduler[channel]; + if (!scheduler) { + scheduler = new RunOnceScheduler(() => { + if (this.isVisible(channel)) { + this.appendOutput(channel); + } + }, OutputContentProvider.OUTPUT_DELAY); + + this.appendOutputScheduler[channel] = scheduler; + this.toDispose.push(scheduler); + } + + if (scheduler.isScheduled()) { + return; // only if not already scheduled + } + + scheduler.schedule(); + } + + private appendOutput(channel?: string): void { + if (!channel) { + const activeChannel = this.outputService.getActiveChannel(); + channel = activeChannel && activeChannel.id; + } + + if (!channel) { + return; // return if we do not have a valid channel to append to + } + + const model = this.getModel(channel); + if (!model) { + return; // only react if we have a known model + } + + const bufferedOutput = this.bufferedOutput[channel]; + if (!bufferedOutput) { + return; // return if nothing to append + } + + // just fill in the full (trimmed) output if we exceed max length + if (model.getValueLength() + bufferedOutput.length > MAX_OUTPUT_LENGTH) { + model.setValue(this.outputService.getChannel(channel).output); + } + + // otherwise append + else { + const lastLine = model.getLineCount(); + const lastLineMaxColumn = model.getLineMaxColumn(lastLine); + + model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), bufferedOutput)]); + } + + // reveal last line + const panel = this.panelService.getActivePanel(); + (panel).revealLastLine(true); + } + + private isVisible(channel: string): boolean { + const panel = this.panelService.getActivePanel(); + + return panel && panel.getId() === OUTPUT_PANEL_ID && this.outputService.getActiveChannel().id === channel; + } + + public provideTextContent(resource: URI): TPromise { + const content = this.outputService.getChannel(resource.fsPath).output; + + let codeEditorModel = this.modelService.getModel(resource); + if (!codeEditorModel) { + codeEditorModel = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), resource); + } + + return TPromise.as(codeEditorModel); + } + + public dispose(): void { + this.toDispose = dispose(this.toDispose); + } } \ No newline at end of file diff --git a/src/vs/workbench/parts/output/common/output.ts b/src/vs/workbench/parts/output/common/output.ts index 9e2e8d41c10..eb3de6fec37 100644 --- a/src/vs/workbench/parts/output/common/output.ts +++ b/src/vs/workbench/parts/output/common/output.ts @@ -7,25 +7,28 @@ import { TPromise } from 'vs/base/common/winjs.base'; import Event from 'vs/base/common/event'; import { Registry } from 'vs/platform/platform'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditor } from 'vs/platform/editor/common/editor'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; +import nls = require('vs/nls'); +import URI from 'vs/base/common/uri'; /** * Mime type used by the output editor. */ export const OUTPUT_MIME = 'text/x-code-output'; +/** + * Output resource scheme. + */ +export const OUTPUT_SCHEME = 'output'; + /** * Id used by the output editor. */ export const OUTPUT_MODE_ID = 'Log'; -/** - * Output editor input id. - */ -export const OUTPUT_EDITOR_INPUT_ID = 'vs.output'; - /** * Output panel id */ @@ -150,3 +153,20 @@ class OutputChannelRegistry implements IOutputChannelRegistry { } Registry.add(Extensions.OutputChannels, new OutputChannelRegistry()); + +export class OutputEditors { + + private static instances: { [channel: string]: ResourceEditorInput; } = Object.create(null); + + public static getInstance(instantiationService: IInstantiationService, channel: IOutputChannel): ResourceEditorInput { + if (OutputEditors.instances[channel.id]) { + return OutputEditors.instances[channel.id]; + } + + const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id }); + + OutputEditors.instances[channel.id] = instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "Output"), channel ? nls.localize('channel', "for '{0}'", channel.label) : '', resource); + + return OutputEditors.instances[channel.id]; + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/media/preferences.css b/src/vs/workbench/parts/preferences/browser/media/preferences.css index 420c2d86178..6c31b27d5e7 100644 --- a/src/vs/workbench/parts/preferences/browser/media/preferences.css +++ b/src/vs/workbench/parts/preferences/browser/media/preferences.css @@ -102,10 +102,6 @@ color: white; } -.monaco-editor .settings-group-title-widget .title-container > * { - vertical-align: middle; - display: inline-block; -} .monaco-editor.vs-dark .settings-group-title-widget .title-container.focused, .monaco-editor.vs .settings-group-title-widget .title-container.focused { @@ -133,6 +129,11 @@ height: 16px; } +.monaco-editor .settings-group-title-widget .title-container > div { + vertical-align: middle; + display: inline-block; +} + .monaco-editor.vs-dark .settings-group-title-widget .title-container .expand-collapse-icon, .monaco-editor.hc-black .settings-group-title-widget .title-container .expand-collapse-icon { background: url(expanded-dark.svg) 50% 50% no-repeat; @@ -147,19 +148,16 @@ background: url(collapsed-dark.svg) 50% 50% no-repeat; } -.monaco-editor .view-line:hover .copySetting:after { - cursor: pointer; - content:" "; +.monaco-editor .edit-preferences-widget { background: url('edit.svg') center center no-repeat; - margin-left: 1em; - display:inline-block; - position: absolute; - height:100%; + transform: rotate(-90deg); width:16px; + height: 16px; + cursor: pointer; } -.monaco-editor.hc-black .view-line:hover .copySetting:after, -.monaco-editor.vs-dark .view-line:hover .copySetting:after { +.monaco-editor.hc-black .edit-preferences-widget, +.monaco-editor.vs-dark .edit-preferences-widget { background: url('edit_inverse.svg') center center no-repeat; } diff --git a/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts index 8ba65969ac6..a030a6fca95 100644 --- a/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts @@ -20,6 +20,8 @@ import { IPreferencesService, CONTEXT_DEFAULT_SETTINGS_EDITOR, DEFAULT_EDITOR_CO import { PreferencesService } from 'vs/workbench/parts/preferences/browser/preferencesService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { PreferencesContentProvider } from 'vs/workbench/parts/preferences/common/preferencesContentProvider'; registerSingleton(IPreferencesService, PreferencesService); @@ -77,4 +79,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }, when: ContextKeyExpr.and(CONTEXT_DEFAULT_SETTINGS_EDITOR), group: 'navigation' -}); \ No newline at end of file +}); + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PreferencesContentProvider); \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 51d398365e8..96c0a7da7ba 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -15,11 +15,10 @@ import { ArrayIterator } from 'vs/base/common/iterator'; import { IAction } from 'vs/base/common/actions'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import Event, { Emitter } from 'vs/base/common/event'; -import { LinkedMap as Map } from 'vs/base/common/map'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { Registry } from 'vs/platform/platform'; -import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; +import { Registry } from 'vs/platform/platform'; +import { EditorOptions, toResource } from 'vs/workbench/common/editor'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -30,27 +29,28 @@ import { Range } from 'vs/editor/common/core/range'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IPreferencesService, ISettingsGroup, ISetting, IPreferencesEditorModel, IFilterResult, CONTEXT_DEFAULT_SETTINGS_EDITOR, - DEFAULT_EDITOR_COMMAND_COLLAPSE_ALL, DEFAULT_EDITOR_COMMAND_FOCUS_SEARCH + DEFAULT_EDITOR_COMMAND_COLLAPSE_ALL, DEFAULT_EDITOR_COMMAND_FOCUS_SEARCH, ISettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferences'; import { SettingsEditorModel, DefaultSettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { ICodeEditor, IEditorMouseEvent, IEditorContributionCtor } from 'vs/editor/browser/editorBrowser'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { DefaultSettingsHeaderWidget, SettingsGroupTitleWidget, SettingsCountWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; +import { IContextMenuService, ContextSubMenu } from 'vs/platform/contextview/browser/contextView'; +import { DefaultSettingsHeaderWidget, SettingsGroupTitleWidget, SettingsCountWidget, EditPreferenceWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { CommonEditorRegistry, EditorCommand, Command } from 'vs/editor/common/editorCommonExtensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IMessageService } from 'vs/platform/message/common/message'; +import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; +import { RangeHighlightDecorations } from 'vs/workbench/common/editor/rangeDecorations'; +import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; // Ignore following contributions import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; @@ -65,8 +65,10 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { private _willDispose = new Emitter(); public willDispose: Event = this._willDispose.event; - constructor(resource: URI, @ITextModelResolverService textModelResolverService: ITextModelResolverService) { - super(nls.localize('settingsEditorName', "Default Settings"), '', resource, textModelResolverService); + constructor(defaultSettingsResource: URI, + @ITextModelResolverService textModelResolverService: ITextModelResolverService + ) { + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService); } getTypeId(): string { @@ -97,21 +99,16 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { export class DefaultPreferencesEditor extends BaseTextEditor { public static ID: string = 'workbench.editor.defaultPreferences'; - private static VIEW_STATE: Map = new Map(); - private inputDisposeListener; private defaultSettingHeaderWidget: DefaultSettingsHeaderWidget; - private delayedFilterLogging: Delayer; constructor( @ITelemetryService telemetryService: ITelemetryService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @IMessageService messageService: IMessageService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IPreferencesService private preferencesService: IPreferencesService, @@ -119,7 +116,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { @IModeService private modeService: IModeService, @ITextFileService textFileService: ITextFileService ) { - super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, contextService, storageService, messageService, configurationService, editorService, themeService, textFileService); + super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService); this.delayedFilterLogging = new Delayer(1000); } @@ -145,7 +142,6 @@ export class DefaultPreferencesEditor extends BaseTextEditor { options.folding = false; options.renderWhitespace = 'none'; options.wrappingColumn = 0; - options.overviewRulerLanes = 0; options.renderIndentGuides = false; options.rulers = []; } @@ -153,9 +149,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { } setInput(input: DefaultPreferencesEditorInput, options: EditorOptions): TPromise { - this.listenToInput(input); - return super.setInput(input, options) - .then(() => this.updateInput()); + return super.setInput(input, options).then(() => this.updateInput()); } public layout(dimension: Dimension) { @@ -177,13 +171,18 @@ export class DefaultPreferencesEditor extends BaseTextEditor { private updateInput(): TPromise { return this.input.resolve() - .then(editorModel => editorModel.load()) - .then(editorModel => this.getControl().setModel((editorModel).textEditorModel)); + .then(editorModel => TPromise.join([ + editorModel.load(), + // Default preferences editor is always part of side by side editor hence getting the master preferences model from active editor + // TODO:@sandy check with Ben + this.preferencesService.resolvePreferencesEditorModel(toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true })) + ])) + .then(([editorModel, preferencesModel]) => (this.getControl()).setModels((editorModel).textEditorModel, preferencesModel)); } private filterPreferences(filter: string) { this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(filter)); - (this.getDefaultPreferencesContribution().getPreferencesRenderer()).filterPreferences(filter); + (this.getDefaultPreferencesContribution().getPreferencesRenderer()).filterPreferences(filter.trim()); } private focusNextPreference() { @@ -192,42 +191,11 @@ export class DefaultPreferencesEditor extends BaseTextEditor { public clearInput(): void { this.getControl().setModel(null); - this.saveState(this.input); - if (this.inputDisposeListener) { - this.inputDisposeListener.dispose(); - } super.clearInput(); } private getDefaultPreferencesContribution(): PreferencesEditorContribution { - return (this.getControl()).getContribution(PreferencesEditorContribution.ID); - } - - protected restoreViewState(input: EditorInput) { - const viewState = DefaultPreferencesEditor.VIEW_STATE.get((input).getResource()); - if (viewState) { - this.getControl().restoreViewState(viewState); - } - } - - private saveState(input: DefaultPreferencesEditorInput) { - const state = this.getControl().saveViewState(); - if (state) { - const resource = input.getResource(); - if (DefaultPreferencesEditor.VIEW_STATE.has(resource)) { - DefaultPreferencesEditor.VIEW_STATE.delete(resource); - } - DefaultPreferencesEditor.VIEW_STATE.set(resource, state); - } - } - - private listenToInput(input: EditorInput) { - if (this.inputDisposeListener) { - this.inputDisposeListener.dispose(); - } - if (input instanceof DefaultPreferencesEditorInput) { - this.inputDisposeListener = (input).willDispose(() => this.saveState(input)); - } + return (this.getControl()).getContribution(DefaultSettingsEditorContribution.ID); } private reportFilteringUsed(filter: string): void { @@ -239,6 +207,8 @@ export class DefaultPreferencesEditor extends BaseTextEditor { class DefaultPreferencesCodeEditor extends CodeEditor { + private _settingsModel: SettingsEditorModel; + protected _getContributions(): IEditorContributionCtor[] { let contributions = super._getContributions(); let skipContributions = [FoldingController.prototype, SelectionHighlighter.prototype, FindController.prototype]; @@ -246,16 +216,25 @@ class DefaultPreferencesCodeEditor extends CodeEditor { contributions.push(DefaultSettingsEditorContribution); return contributions; } + + setModels(model: editorCommon.IModel, settingsModel: SettingsEditorModel): void { + this._settingsModel = settingsModel; + return super.setModel(model); + } + + get settingsModel(): SettingsEditorModel { + return this._settingsModel; + } } export interface IPreferencesRenderer { render(); + updatePreference(setting: ISetting, value: any): void; dispose(); } export abstract class PreferencesEditorContribution extends Disposable implements editorCommon.IEditorContribution { - static ID: string = 'editor.contrib.preferences'; private preferencesRenderer: IPreferencesRenderer; constructor(protected editor: ICodeEditor, @@ -282,15 +261,12 @@ export abstract class PreferencesEditorContribution extends Disposable implement } } - getId(): string { - return PreferencesEditorContribution.ID; - } - getPreferencesRenderer(): IPreferencesRenderer { return this.preferencesRenderer; } protected abstract createPreferencesRenderer(editorModel: IPreferencesEditorModel): IPreferencesRenderer + abstract getId(): string; private disposePreferencesRenderer() { if (this.preferencesRenderer) { @@ -306,16 +282,30 @@ export abstract class PreferencesEditorContribution extends Disposable implement } export class DefaultSettingsEditorContribution extends PreferencesEditorContribution implements editorCommon.IEditorContribution { + + static ID: string = 'editor.contrib.defaultsettings'; + protected createPreferencesRenderer(editorModel: IPreferencesEditorModel): IPreferencesRenderer { if (editorModel instanceof DefaultSettingsEditorModel) { - return this.instantiationService.createInstance(DefaultSettingsRenderer, this.editor, editorModel); + return this.instantiationService.createInstance(DefaultSettingsRenderer, this.editor, editorModel, (this.editor).settingsModel); } return null; } + + getId(): string { + return DefaultSettingsEditorContribution.ID; + } } @editorContribution export class SettingsEditorContribution extends PreferencesEditorContribution implements editorCommon.IEditorContribution { + + static ID: string = 'editor.contrib.settings'; + + getId(): string { + return SettingsEditorContribution.ID; + } + protected createPreferencesRenderer(editorModel: IPreferencesEditorModel): IPreferencesRenderer { if (editorModel instanceof SettingsEditorModel) { return this.instantiationService.createInstance(SettingsRenderer, this.editor, editorModel); @@ -326,20 +316,35 @@ export class SettingsEditorContribution extends PreferencesEditorContribution im export class SettingsRenderer extends Disposable implements IPreferencesRenderer { - private copySettingActionRenderer: CopySettingActionRenderer; + private initializationPromise: TPromise; + private settingHighlighter: SettingHighlighter; + private editSettingActionRenderer: EditSettingRenderer; private modelChangeDelayer: Delayer = new Delayer(200); constructor(protected editor: ICodeEditor, protected settingsEditorModel: SettingsEditorModel, @IPreferencesService protected preferencesService: IPreferencesService, + @ITelemetryService private telemetryService: ITelemetryService, + @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, + @IMessageService private messageService: IMessageService, @IInstantiationService protected instantiationService: IInstantiationService ) { super(); - this.copySettingActionRenderer = this._register(instantiationService.createInstance(CopySettingActionRenderer, editor, false)); - this._register(editor.getModel().onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); + this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor)); + this.initializationPromise = this.initialize(); } public render(): void { - this.copySettingActionRenderer.render(this.settingsEditorModel.settingsGroups); + this.initializationPromise.then(() => this.editSettingActionRenderer.render(this.settingsEditorModel.settingsGroups)); + } + + private initialize(): TPromise { + return this.preferencesService.createDefaultPreferencesEditorModel(this.preferencesService.defaultSettingsResource) + .then(defaultSettingsModel => { + this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.settingsEditorModel, defaultSettingsModel, this.settingHighlighter)); + this._register(this.editor.getModel().onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); + this._register(this.editSettingActionRenderer.onUpdateSetting(({setting, value}) => this.updatePreference(setting, value))); + return null; + }); } private onModelChanged(): void { @@ -349,32 +354,51 @@ export class SettingsRenderer extends Disposable implements IPreferencesRenderer } this.render(); } + + public updatePreference(setting: ISetting, value: any): void { + this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [setting.key] }); + this.configurationEditingService.writeConfiguration(this.settingsEditorModel.configurationTarget, { key: setting.key, value }, { writeToBuffer: true, autoSave: true }) + .then(() => this.onSettingUpdated(setting), error => this.messageService.show(Severity.Error, error)); + } + + private onSettingUpdated(setting: ISetting) { + this.editor.focus(); + setting = this.settingsEditorModel.getSetting(setting.key); + // TODO:@sandy Selection range should be template range + this.editor.setSelection(setting.valueRange); + this.settingHighlighter.highlight(this.settingsEditorModel.getSetting(setting.key), true); + } } export class DefaultSettingsRenderer extends Disposable implements IPreferencesRenderer { private defaultSettingsEditorContextKey: IContextKey; + private settingHighlighter: SettingHighlighter; private settingsGroupTitleRenderer: SettingsGroupTitleRenderer; private filteredMatchesRenderer: FilteredMatchesRenderer; - private focusNextSettingRenderer: FocusNextSettingRenderer; + private filteredSettingsNavigationRenderer: FilteredSettingsNavigationRenderer; private hiddenAreasRenderer: HiddenAreasRenderer; - private copySettingActionRenderer: CopySettingActionRenderer; + private editSettingActionRenderer: EditSettingRenderer; private settingsCountWidget: SettingsCountWidget; - constructor(protected editor: ICodeEditor, protected settingsEditorModel: DefaultSettingsEditorModel, + constructor(protected editor: ICodeEditor, protected defaultSettingsEditorModel: DefaultSettingsEditorModel, + private settingsEditorModel: SettingsEditorModel, @IPreferencesService protected preferencesService: IPreferencesService, @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IInstantiationService protected instantiationService: IInstantiationService ) { super(); this.defaultSettingsEditorContextKey = CONTEXT_DEFAULT_SETTINGS_EDITOR.bindTo(contextKeyService); + this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor)); this.settingsGroupTitleRenderer = this._register(instantiationService.createInstance(SettingsGroupTitleRenderer, editor)); this.filteredMatchesRenderer = this._register(instantiationService.createInstance(FilteredMatchesRenderer, editor)); - this.focusNextSettingRenderer = this._register(instantiationService.createInstance(FocusNextSettingRenderer, editor)); - this.copySettingActionRenderer = this._register(instantiationService.createInstance(CopySettingActionRenderer, editor, true)); - this.settingsCountWidget = this._register(instantiationService.createInstance(SettingsCountWidget, editor, this.getCount(settingsEditorModel.settingsGroups))); - const paranthesisHidingRenderer = this._register(instantiationService.createInstance(StaticContentHidingRenderer, editor, settingsEditorModel.settingsGroups)); + this.filteredSettingsNavigationRenderer = this._register(instantiationService.createInstance(FilteredSettingsNavigationRenderer, editor, this.settingHighlighter)); + this.editSettingActionRenderer = this._register(instantiationService.createInstance(EditSettingRenderer, editor, defaultSettingsEditorModel, settingsEditorModel, this.settingHighlighter)); + this._register(this.editSettingActionRenderer.onUpdateSetting(({setting, value}) => this.updatePreference(setting, value))); + this.settingsCountWidget = this._register(instantiationService.createInstance(SettingsCountWidget, editor, this.getCount(defaultSettingsEditorModel.settingsGroups))); + const paranthesisHidingRenderer = this._register(instantiationService.createInstance(StaticContentHidingRenderer, editor, defaultSettingsEditorModel.settingsGroups)); this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, paranthesisHidingRenderer])); this._register(this.settingsGroupTitleRenderer.onHiddenAreasChanged(() => this.hiddenAreasRenderer.render())); @@ -382,31 +406,32 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR public render() { this.defaultSettingsEditorContextKey.set(true); - this.settingsGroupTitleRenderer.render(this.settingsEditorModel.settingsGroups); - this.copySettingActionRenderer.render(this.settingsEditorModel.settingsGroups); + this.settingsGroupTitleRenderer.render(this.defaultSettingsEditorModel.settingsGroups); + this.editSettingActionRenderer.render(this.defaultSettingsEditorModel.settingsGroups); this.settingsCountWidget.render(); - this.focusNextSettingRenderer.render([]); + this.hiddenAreasRenderer.render(); + this.filteredSettingsNavigationRenderer.render([]); this.settingsGroupTitleRenderer.showGroup(1); this.hiddenAreasRenderer.render(); } public filterPreferences(filter: string) { - const filterResult = this.settingsEditorModel.filterSettings(filter); + const filterResult = this.defaultSettingsEditorModel.filterSettings(filter); this.filteredMatchesRenderer.render(filterResult); this.settingsGroupTitleRenderer.render(filterResult.filteredGroups); this.settingsCountWidget.show(this.getCount(filterResult.filteredGroups)); if (!filter) { - this.focusNextSettingRenderer.render([]); + this.filteredSettingsNavigationRenderer.render([]); this.settingsGroupTitleRenderer.showGroup(1); } else { - this.focusNextSettingRenderer.render(filterResult.filteredGroups); + this.filteredSettingsNavigationRenderer.render(filterResult.filteredGroups); } this.hiddenAreasRenderer.render(); } public focusNextSetting(): void { - const setting = this.focusNextSettingRenderer.focusNext(); + const setting = this.filteredSettingsNavigationRenderer.next(); if (setting) { this.settingsGroupTitleRenderer.showSetting(setting); } @@ -416,6 +441,23 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR this.settingsGroupTitleRenderer.collapseAll(); } + public updatePreference(setting: ISetting, value: any): void { + const settingsEditor = this.getEditableSettingsEditor(); + if (settingsEditor) { + settingsEditor.getContribution(SettingsEditorContribution.ID).getPreferencesRenderer().updatePreference(setting, value); + } + } + + private getEditableSettingsEditor(): editorCommon.ICommonCodeEditor { + return this.editorService.getVisibleEditors() + .filter(editor => { + if (editorCommon.isCommonCodeEditor(editor.getControl())) { + return (editor.getControl()).getModel().uri.fsPath === this.settingsEditorModel.uri.fsPath; + } + }) + .map(editor => editor.getControl())[0]; + } + private getCount(settingsGroups: ISettingsGroup[]): number { let count = 0; for (const group of settingsGroups) { @@ -628,9 +670,9 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr }); } else { for (const section of group.sections) { - if (section.descriptionRange) { - if (!this.containsLine(section.descriptionRange.startLineNumber, filteredGroup)) { - notMatchesRanges.push(this.createCompleteRange(section.descriptionRange, model)); + if (section.titleRange) { + if (!this.containsLine(section.titleRange.startLineNumber, filteredGroup)) { + notMatchesRanges.push(this.createCompleteRange(section.titleRange, model)); } } for (const setting of section.settings) { @@ -650,7 +692,7 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr } for (const section of settingsGroup.sections) { - if (section.descriptionRange && lineNumber >= section.descriptionRange.startLineNumber && lineNumber <= section.descriptionRange.endLineNumber) { + if (section.titleRange && lineNumber >= section.titleRange.startLineNumber && lineNumber <= section.titleRange.endLineNumber) { return true; } @@ -682,44 +724,25 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr } } -export class FocusNextSettingRenderer extends Disposable { +class FilteredSettingsNavigationRenderer extends Disposable { private iterator: ArrayIterator; - private decorationIds: string[] = []; - constructor(private editor: ICodeEditor) { + constructor(private editor: ICodeEditor, private settingHighlighter: SettingHighlighter) { super(); } - public focusNext(): ISetting { - this.clear(); + public next(): ISetting { let setting = this.iterator.next() || this.iterator.first(); if (setting) { - const model = this.editor.getModel(); - this.editor.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, [{ - range: { - startLineNumber: setting.valueRange.startLineNumber, - startColumn: model.getLineMinColumn(setting.valueRange.startLineNumber), - endLineNumber: setting.valueRange.endLineNumber, - endColumn: model.getLineMaxColumn(setting.valueRange.endLineNumber) - }, - options: { - stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'rangeHighlight', - isWholeLine: true - } - }]); - }); - this.editor.revealLinesInCenterIfOutsideViewport(setting.valueRange.startLineNumber, setting.valueRange.endLineNumber - 1); + this.settingHighlighter.highlight(setting, true); return setting; } return null; } public render(filteredGroups: ISettingsGroup[]) { - this.clear(); - + this.settingHighlighter.clear(true); const settings: ISetting[] = []; for (const group of filteredGroups) { for (const section of group.sections) { @@ -728,135 +751,148 @@ export class FocusNextSettingRenderer extends Disposable { } this.iterator = new ArrayIterator(settings); } - - private clear() { - this.editor.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []); - }); - } - - public dispose() { - this.clear(); - super.dispose(); - } } -export class CopySettingActionRenderer extends Disposable { +class EditSettingRenderer extends Disposable { + + private editPreferenceWidgetForCusorPosition: EditPreferenceWidget; + private editPreferenceWidgetForMouseMove: EditPreferenceWidget; - private decorationIds: string[] = []; private settingsGroups: ISettingsGroup[]; - private model: editorCommon.IModel; + private toggleEditPreferencesForMouseMoveDelayer: Delayer; - constructor(private editor: ICodeEditor, private isDefaultSettings: boolean, - @IPreferencesService private settingsService: IPreferencesService, + private _onUpdateSetting: Emitter<{ setting: ISetting, value: any }> = new Emitter<{ setting: ISetting, value: any }>(); + public readonly onUpdateSetting: Event<{ setting: ISetting, value: any }> = this._onUpdateSetting.event; + + constructor(private editor: ICodeEditor, private masterSettingsModel: ISettingsEditorModel, + private otherSettingsModel: ISettingsEditorModel, + private settingHighlighter: SettingHighlighter, + @IPreferencesService private preferencesService: IPreferencesService, + @IInstantiationService private instantiationService: IInstantiationService, @IContextMenuService private contextMenuService: IContextMenuService ) { super(); - this._register(editor.onMouseUp(e => this.onEditorMouseUp(e))); + + this.editPreferenceWidgetForCusorPosition = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor)); + this.editPreferenceWidgetForMouseMove = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor)); + this.toggleEditPreferencesForMouseMoveDelayer = new Delayer(75); + + this._register(this.editPreferenceWidgetForCusorPosition.onClick(setting => this.onEditSettingClicked(this.editPreferenceWidgetForCusorPosition))); + this._register(this.editPreferenceWidgetForMouseMove.onClick(setting => this.onEditSettingClicked(this.editPreferenceWidgetForMouseMove))); + + this._register(this.editPreferenceWidgetForCusorPosition.onMouseOver(setting => this.onMouseOver(this.editPreferenceWidgetForCusorPosition))); + this._register(this.editPreferenceWidgetForMouseMove.onMouseOver(setting => this.onMouseOver(this.editPreferenceWidgetForMouseMove))); + + this._register(this.editor.onDidChangeCursorPosition(positionChangeEvent => this.onPositionChanged(positionChangeEvent))); + this._register(this.editor.onMouseMove(mouseMoveEvent => this.onMouseMoved(mouseMoveEvent))); } public render(settingsGroups: ISettingsGroup[]): void { - this.model = this.editor.getModel(); + this.editPreferenceWidgetForCusorPosition.hide(); + this.editPreferenceWidgetForMouseMove.hide(); this.settingsGroups = settingsGroups; - this.model.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []); - }); - this.model.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, this.createDecorations(this.model)); - }); - } - private createDecorations(model: editorCommon.IModel): editorCommon.IModelDeltaDecoration[] { - let result: editorCommon.IModelDeltaDecoration[] = []; - for (const settingsGroup of this.settingsGroups) { - for (const settingsSection of settingsGroup.sections) { - for (const setting of settingsSection.settings) { - const decoration = this.createSettingDecoration(setting, model); - if (decoration) { - result.push(decoration); - } - } - } + const settings = this.getSettings(this.editor.getPosition().lineNumber); + if (settings.length) { + this.showEditPreferencesWidget(this.editPreferenceWidgetForCusorPosition, settings); } - return result; } - private createSettingDecoration(setting: ISetting, model: editorCommon.IModel): editorCommon.IModelDeltaDecoration { - const jsonSchema: IJSONSchema = this.getConfigurationsMap()[setting.key]; - if (jsonSchema) { - const canChooseValue = jsonSchema.enum || jsonSchema.type === 'boolean'; - if (this.isDefaultSettings || canChooseValue) { - const lineNumber = setting.keyRange.startLineNumber; - return { - range: { - startLineNumber: lineNumber, - startColumn: model.getLineMaxColumn(lineNumber), - endLineNumber: lineNumber, - endColumn: model.getLineMaxColumn(lineNumber), - }, - options: { - afterContentClassName: 'copySetting', - stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - hoverMessage: canChooseValue ? this.isDefaultSettings ? nls.localize('selectAndCopySetting', "Select a value and copy to Settings") - : nls.localize('selectValue', "Select a value") : nls.localize('copy', "Copy to Settings") - } - }; - } + private isDefaultSettings(): boolean { + return this.masterSettingsModel instanceof DefaultSettingsEditorModel; + } + + private onPositionChanged(positionChangeEvent: editorCommon.ICursorPositionChangedEvent) { + this.editPreferenceWidgetForMouseMove.hide(); + const settings = this.getSettings(positionChangeEvent.position.lineNumber); + if (settings.length) { + this.showEditPreferencesWidget(this.editPreferenceWidgetForCusorPosition, settings); + } else { + this.editPreferenceWidgetForCusorPosition.hide(); + } + } + + private onMouseMoved(mouseMoveEvent: IEditorMouseEvent): void { + const editPreferenceWidget = this.getEditPreferenceWidgetUnderMouse(mouseMoveEvent); + if (editPreferenceWidget) { + this.onMouseOver(editPreferenceWidget); + return; + } + this.settingHighlighter.clear(); + this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => this.toggleEidtPreferenceWidgetForMouseMove(mouseMoveEvent)); + } + + private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget { + if (mouseMoveEvent.event.target === this.editPreferenceWidgetForMouseMove.getDomNode()) { + return this.editPreferenceWidgetForMouseMove; + } + if (mouseMoveEvent.event.target === this.editPreferenceWidgetForCusorPosition.getDomNode()) { + return this.editPreferenceWidgetForCusorPosition; } return null; } - private onEditorMouseUp(e: IEditorMouseEvent): void { - let range = e.target.range; - if (!range || !range.isEmpty) { - return; - } - if (!e.event.leftButton) { - return; - } - - switch (e.target.type) { - case editorCommon.MouseTargetType.CONTENT_EMPTY: - if (DOM.hasClass(e.target.element, 'copySetting')) { - this.onClick(e); - } - return; - default: - return; + private toggleEidtPreferenceWidgetForMouseMove(mouseMoveEvent: IEditorMouseEvent): void { + const settings = mouseMoveEvent.target.position ? this.getSettings(mouseMoveEvent.target.position.lineNumber) : null; + if (settings && settings.length) { + this.showEditPreferencesWidget(this.editPreferenceWidgetForMouseMove, settings); + } else { + this.editPreferenceWidgetForMouseMove.hide(); } } - private getConfigurationsMap(): { [qualifiedKey: string]: IJSONSchema } { - return Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + private showEditPreferencesWidget(editPreferencesWidget: EditPreferenceWidget, settings: ISetting[]) { + editPreferencesWidget.show(settings[0].valueRange.startLineNumber, settings); + editPreferencesWidget.getDomNode().title = nls.localize('editTtile', "Edit"); } - private onClick(e: IEditorMouseEvent) { - const setting = this.getSetting(e.target.range.startLineNumber); - if (setting) { - let jsonSchema: IJSONSchema = this.getConfigurationsMap()[setting.key]; - const actions = this.getActions(setting, jsonSchema); - let elementPosition = DOM.getDomNodePagePosition(e.target.element); - const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => TPromise.wrap(actions) - }); - } + private getSettings(lineNumber: number): ISetting[] { + const configurationMap = this.getConfigurationsMap(); + return this.getSettingsAtLineNumber(lineNumber).filter(setting => { + let jsonSchema: IJSONSchema = configurationMap[setting.key]; + return jsonSchema && (this.isDefaultSettings() || jsonSchema.type === 'boolean' || jsonSchema.enum); + }); } - private getSetting(lineNumber: number): ISetting { + private getSettingsAtLineNumber(lineNumber: number): ISetting[] { + const settings = []; for (const group of this.settingsGroups) { + if (group.range.startLineNumber > lineNumber) { + break; + } if (lineNumber >= group.range.startLineNumber && lineNumber <= group.range.endLineNumber) { for (const section of group.sections) { for (const setting of section.settings) { - if (lineNumber >= setting.keyRange.startLineNumber && lineNumber <= setting.keyRange.endLineNumber) { - return setting; + if (setting.range.startLineNumber > lineNumber) { + break; + } + if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) { + settings.push(setting); } } } } } - return null; + return settings; + } + + private onMouseOver(editPreferenceWidget: EditPreferenceWidget): void { + this.settingHighlighter.highlight(editPreferenceWidget.preferences[0]); + } + + private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget): void { + const elementPosition = DOM.getDomNodePagePosition(editPreferenceWidget.getDomNode()); + const anchor = { x: elementPosition.left + elementPosition.width, y: elementPosition.top + elementPosition.height + 10 }; + const actions = this.getSettingsAtLineNumber(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key]) + : editPreferenceWidget.preferences.map(setting => new ContextSubMenu(setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key]))); + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => TPromise.wrap(actions) + }); + } + + private getConfigurationsMap(): { [qualifiedKey: string]: IJSONSchema } { + return Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); } private getActions(setting: ISetting, jsonSchema: IJSONSchema): IAction[] { @@ -865,12 +901,12 @@ export class CopySettingActionRenderer extends Disposable { id: 'truthyValue', label: 'true', enabled: true, - run: () => this.settingsService.copyConfiguration({ key: setting.key, value: true }) + run: () => this.updateSetting(setting, true) }, { id: 'falsyValue', label: 'false', enabled: true, - run: () => this.settingsService.copyConfiguration({ key: setting.key, value: false }) + run: () => this.updateSetting(setting, false) }]; } if (jsonSchema.enum) { @@ -879,27 +915,64 @@ export class CopySettingActionRenderer extends Disposable { id: value, label: JSON.stringify(value), enabled: true, - run: () => this.settingsService.copyConfiguration({ key: setting.key, value }) + run: () => this.updateSetting(setting, value) }; }); } - return [{ - id: 'copyToSettings', - label: nls.localize('copyToSettings', "Copy to Settings"), - enabled: true, - run: () => this.settingsService.copyConfiguration(setting) - }]; + return this.getDefaultActions(setting); } - public dispose() { - if (this.model) { - this.model.deltaDecorations(this.decorationIds, []); + private getDefaultActions(setting: ISetting): IAction[] { + const settingInOtherModel = this.otherSettingsModel.getSetting(setting.key); + if (this.isDefaultSettings()) { + return [{ + id: 'setDefaultValue', + label: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"), + enabled: true, + run: () => this.updateSetting(setting, setting.value) + }]; } - super.dispose(); + return []; + } + + private updateSetting(setting: ISetting, value: any): void { + this._onUpdateSetting.fire({ setting, value }); } } -const DefaultSettingsEditorCommand = EditorCommand.bindToContribution((editor: editorCommon.ICommonCodeEditor) => editor.getContribution(PreferencesEditorContribution.ID)); +class SettingHighlighter extends Disposable { + + private fixedHighlighter: RangeHighlightDecorations; + private volatileHighlighter: RangeHighlightDecorations; + + constructor(private editor: editorCommon.ICommonCodeEditor, @IInstantiationService instantiationService: IInstantiationService) { + super(); + this.fixedHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); + this.volatileHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); + } + + highlight(setting: ISetting, fix: boolean = false) { + this.volatileHighlighter.removeHighlightRange(); + this.fixedHighlighter.removeHighlightRange(); + + const highlighter = fix ? this.fixedHighlighter : this.volatileHighlighter; + highlighter.highlightRange({ + range: setting.valueRange, + resource: this.editor.getModel().uri + }, this.editor); + + this.editor.revealLinesInCenterIfOutsideViewport(setting.valueRange.startLineNumber, setting.valueRange.endLineNumber - 1); + } + + clear(fix: boolean = false): void { + this.volatileHighlighter.removeHighlightRange(); + if (fix) { + this.fixedHighlighter.removeHighlightRange(); + } + } +} + +const DefaultSettingsEditorCommand = EditorCommand.bindToContribution((editor: editorCommon.ICommonCodeEditor) => editor.getContribution(DefaultSettingsEditorContribution.ID)); CommonEditorRegistry.registerEditorCommand(new DefaultSettingsEditorCommand({ id: DEFAULT_EDITOR_COMMAND_COLLAPSE_ALL, diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts index b9b45f8167f..24d871f3a58 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts @@ -10,8 +10,7 @@ import URI from 'vs/base/common/uri'; import { LinkedMap as Map } from 'vs/base/common/map'; import * as labels from 'vs/base/common/labels'; import { Disposable } from 'vs/base/common/lifecycle'; -import { parseTree, findNodeAtLocation } from 'vs/base/common/json'; -import { toResource, SideBySideEditorInput, EditorInput } from 'vs/workbench/common/editor'; +import { SideBySideEditorInput, EditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_DEFAULT_PATH } from 'vs/workbench/services/configuration/common/configuration'; @@ -23,9 +22,7 @@ import { IMessageService, Severity, IChoiceService } from 'vs/platform/message/c import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IConfigurationEditingService, ConfigurationTarget, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IPreferencesService, IPreferencesEditorModel } from 'vs/workbench/parts/preferences/common/preferences'; import { SettingsEditorModel, DefaultSettingsEditorModel, DefaultKeybindingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -44,9 +41,6 @@ interface IWorkbenchSettingsConfiguration { export class PreferencesService extends Disposable implements IPreferencesService { - static DEFAULT_SETTINGS_URI: URI = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' }); - static DEFAULT_KEY_BINDINGS_URI: URI = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); - _serviceBrand: any; // TODO:@sandy merge these models into editor inputs by extending resource editor model @@ -74,13 +68,16 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.defaultPreferencesEditorModels = new Map(); } + readonly defaultSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' }); + readonly defaultKeybindingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); + createDefaultPreferencesEditorModel(uri: URI): TPromise { const editorModel = this.defaultPreferencesEditorModels.get(uri); if (editorModel) { return TPromise.as(editorModel); } - if (PreferencesService.DEFAULT_SETTINGS_URI.fsPath === uri.fsPath) { + if (this.defaultSettingsResource.fsPath === uri.fsPath) { return TPromise.join([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()]) .then(result => { const mostCommonSettings = result[1]; @@ -90,7 +87,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - if (PreferencesService.DEFAULT_KEY_BINDINGS_URI.fsPath === uri.fsPath) { + if (this.defaultKeybindingsResource.fsPath === uri.fsPath) { const model = this.instantiationService.createInstance(DefaultKeybindingsEditorModel, uri); this.defaultPreferencesEditorModels.set(uri, model); return TPromise.wrap(model); @@ -128,14 +125,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic openWorkspaceSettings(): TPromise { if (!this.contextService.hasWorkspace()) { this.messageService.show(Severity.Info, nls.localize('openFolderFirst', "Open a folder first to create workspace settings")); - return; + return TPromise.as(null); } return this.openSettings(ConfigurationTarget.WORKSPACE); } openGlobalKeybindingSettings(): TPromise { const emptyContents = '// ' + nls.localize('emptyKeybindingsHeader', "Place your key bindings in this file to overwrite the defaults") + '\n[\n]'; - return this.openTwoEditors(PreferencesService.DEFAULT_KEY_BINDINGS_URI, URI.file(this.environmentService.appKeybindingsPath), emptyContents).then(() => null); + return this.openTwoEditors(this.defaultKeybindingsResource, URI.file(this.environmentService.appKeybindingsPath), emptyContents).then(() => null); } private openEditableSettings(configurationTarget: ConfigurationTarget): TPromise { @@ -147,19 +144,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic })); } - public copyConfiguration(configurationValue: IConfigurationValue): void { - const configurationTarget = this.getConfigurationTargetForCurrentActiveEditor(); - if (configurationTarget !== null) { - this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [configurationValue.key] }); - const editorControl = this.editorService.getActiveEditor().getControl(); - this.configurationEditingService.writeConfiguration(configurationTarget, configurationValue, { writeToBuffer: true, autoSave: true }) - .then(() => { - editorControl.focus(); - editorControl.setSelection(this.getSelectionRange(configurationValue.key, editorControl.getModel())); - }, error => this.messageService.show(Severity.Error, error)); - } - } - private resolveSettingsEditorModel(configurationTarget: ConfigurationTarget): TPromise { const settingsUri = this.getEditableSettingsURI(configurationTarget); if (settingsUri) { @@ -225,12 +209,12 @@ export class PreferencesService extends Disposable implements IPreferencesServic switch (configurationTarget) { case ConfigurationTarget.USER: if (!this.defaultSettingsEditorInputForUser) { - this.defaultSettingsEditorInputForUser = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, PreferencesService.DEFAULT_SETTINGS_URI)); + this.defaultSettingsEditorInputForUser = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.defaultSettingsResource)); } return this.defaultSettingsEditorInputForUser; case ConfigurationTarget.WORKSPACE: if (!this.defaultSettingsEditorInputForWorkspace) { - this.defaultSettingsEditorInputForWorkspace = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, PreferencesService.DEFAULT_SETTINGS_URI)); + this.defaultSettingsEditorInputForWorkspace = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.defaultSettingsResource)); } return this.defaultSettingsEditorInputForWorkspace; } @@ -271,40 +255,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - private getConfigurationTargetForCurrentActiveEditor(): ConfigurationTarget { - const activeEditor = this.editorService.getActiveEditor(); - if (activeEditor) { - const file = toResource(activeEditor.input, { supportSideBySide: true, filter: 'file' }); - if (file) { - return this.getConfigurationTarget(file); - } - } - return null; - } - - private getConfigurationTarget(resource: URI): ConfigurationTarget { - if (this.getEditableSettingsURI(ConfigurationTarget.USER).fsPath === resource.fsPath) { - return ConfigurationTarget.USER; - } - const workspaceSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE); - if (workspaceSettingsUri && workspaceSettingsUri.fsPath === resource.fsPath) { - return ConfigurationTarget.WORKSPACE; - } - return null; - } - - private getSelectionRange(setting: string, model: editorCommon.IModel): editorCommon.IRange { - const tree = parseTree(model.getValue()); - const node = findNodeAtLocation(tree, [setting]); - const position = model.getPositionAt(node.offset); - return { - startLineNumber: position.lineNumber, - startColumn: position.column, - endLineNumber: position.lineNumber, - endColumn: position.column + node.length - }; - } - private fetchMostCommonlyUsedSettings(): TPromise { return TPromise.wrap([ 'editor.fontSize', diff --git a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts index 3a9ca8db9ba..a6260610aec 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts @@ -91,8 +91,11 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { } private layout(): void { - this.titleContainer.style.lineHeight = this.editor.getConfiguration().lineHeight + 3 + 'px'; - this.titleContainer.style.fontSize = this.editor.getConfiguration().fontInfo.fontSize + 'px'; + const configuration = this.editor.getConfiguration(); + const layoutInfo = this.editor.getLayoutInfo(); + this.titleContainer.style.width = layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth + 'px'; + this.titleContainer.style.lineHeight = configuration.lineHeight + 3 + 'px'; + this.titleContainer.style.fontSize = configuration.fontInfo.fontSize + 'px'; const iconSize = this.getIconSize(); this.icon.style.height = `${iconSize}px`; this.icon.style.width = `${iconSize}px`; @@ -344,4 +347,92 @@ export class SettingsCountWidget extends Widget implements IOverlayWidget { preference: null }; } +} + +export class EditPreferenceWidget extends Widget implements IOverlayWidget { + + private static counter: number = 1; + + private _domNode: HTMLElement; + private _visible: boolean; + private _line: number; + private _id: string; + private _preferences: T[]; + + private _onClick: Emitter = new Emitter(); + public get onClick(): Event { return this._onClick.event; } + + private _onMouseOver: Emitter = new Emitter(); + public get onMouseOver(): Event { return this._onMouseOver.event; } + + constructor(private editor: ICodeEditor, + @IContextMenuService contextMenuService: IContextMenuService + ) { + super(); + this._id = 'preferences.editPreferenceWidget' + EditPreferenceWidget.counter++; + this.editor.addOverlayWidget(this); + this._register(this.editor.onDidScrollChange(() => { + if (this._visible) { + this._layout(); + } + })); + } + + public dispose(): void { + this.editor.removeOverlayWidget(this); + super.dispose(); + } + + getId(): string { + return this._id; + } + + getDomNode(): HTMLElement { + if (!this._domNode) { + this._domNode = document.createElement('div'); + this._domNode.style.width = '20px'; + this._domNode.style.height = '20px'; + this._domNode.className = 'edit-preferences-widget hidden'; + this.onclick(this._domNode, e => this._onClick.fire()); + this.onmouseover(this._domNode, e => this._onMouseOver.fire()); + } + return this._domNode; + } + + getPosition(): IOverlayWidgetPosition { + return null; + } + + getLine(): number { + return this._line; + } + + show(line: number, preferences: T[]): void { + this._preferences = preferences; + if (!this._visible || this._line !== line) { + this._line = line; + this._visible = true; + this._layout(); + } + } + + get preferences(): T[] { + return this._preferences; + } + + hide(): void { + if (this._visible) { + this._visible = false; + this._domNode.classList.add('hidden'); + } + } + + private _layout(): void { + const topForLineNumber = this.editor.getTopForLineNumber(this._line); + const editorScrollTop = this.editor.getScrollTop(); + + this._domNode.style.top = `${topForLineNumber - editorScrollTop - 2}px`; + this._domNode.style.left = '0px'; + this._domNode.classList.remove('hidden'); + } } \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index 46ac5b84802..83cafc86be6 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -8,7 +8,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { LinkedMap as Map } from 'vs/base/common/map'; import { IRange } from 'vs/editor/common/editorCommon'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export interface ISettingsGroup { @@ -20,8 +19,8 @@ export interface ISettingsGroup { } export interface ISettingsSection { - descriptionRange?: IRange; - description?: string; + titleRange?: IRange; + title?: string; settings: ISetting[]; } @@ -31,8 +30,8 @@ export interface ISetting { keyRange: IRange; value: any; valueRange: IRange; - description: string; - descriptionRange: IRange; + description: string[]; + descriptionRanges: IRange[]; } export interface IFilterResult { @@ -49,6 +48,7 @@ export interface IPreferencesEditorModel { export interface ISettingsEditorModel extends IPreferencesEditorModel { settingsGroups: ISettingsGroup[]; groupsTerms: string[]; + getSetting(key: string): ISetting; filterSettings(filter: string): IFilterResult; } @@ -60,14 +60,15 @@ export const IPreferencesService = createDecorator('prefere export interface IPreferencesService { _serviceBrand: any; + defaultSettingsResource: URI; + defaultKeybindingsResource: URI; + createDefaultPreferencesEditorModel(uri: URI): TPromise; resolvePreferencesEditorModel(uri: URI): TPromise; openGlobalSettings(): TPromise; openWorkspaceSettings(): TPromise; openGlobalKeybindingSettings(): TPromise; - - copyConfiguration(configurationValue: IConfigurationValue): void; } export const CONTEXT_DEFAULT_SETTINGS_EDITOR = new RawContextKey('defaultSettingsEditor', false); diff --git a/src/vs/workbench/parts/contentprovider/common/contentprovider.contribution.ts b/src/vs/workbench/parts/preferences/common/preferencesContentProvider.ts similarity index 87% rename from src/vs/workbench/parts/contentprovider/common/contentprovider.contribution.ts rename to src/vs/workbench/parts/preferences/common/preferencesContentProvider.ts index a7e0a38b0e8..2d5ef5d8cb7 100644 --- a/src/vs/workbench/parts/contentprovider/common/contentprovider.contribution.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesContentProvider.ts @@ -11,13 +11,13 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IModel } from 'vs/editor/common/editorCommon'; import JSONContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry'); import { Registry } from 'vs/platform/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences'; const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); -export class WorkbenchContentProvider implements IWorkbenchContribution { +export class PreferencesContentProvider implements IWorkbenchContribution { constructor( @IModelService private modelService: IModelService, @@ -58,6 +58,4 @@ export class WorkbenchContentProvider implements IWorkbenchContribution { } }); } -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkbenchContentProvider); \ No newline at end of file +} \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts index fb3d68dd0fc..57ca716dda3 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts @@ -12,16 +12,15 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/platform'; import { visit, JSONVisitor } from 'vs/base/common/json'; import { IModel, IRange } from 'vs/editor/common/editorCommon'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ISettingsEditorModel, IKeybindingsEditorModel, ISettingsGroup, ISetting, IFilterResult, ISettingsSection } from 'vs/workbench/parts/preferences/common/preferences'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IFilter, IMatch, or, matchesContiguousSubString, matchesPrefix, matchesFuzzy, matchesWords } from 'vs/base/common/filters'; +import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters'; export abstract class AbstractSettingsModel extends Disposable { - static _fuzzyFilter: IFilter = or(matchesPrefix, matchesContiguousSubString, matchesWords, matchesFuzzy); - public get groupsTerms(): string[] { return this.settingsGroups.map(group => '@' + group.id); } @@ -53,7 +52,7 @@ export abstract class AbstractSettingsModel extends Disposable { for (const section of group.sections) { const settings: ISetting[] = []; for (const setting of section.settings) { - const settingMatches = this._findMatchesInSetting(filter, regex, setting); + const settingMatches = this._findMatchesInSetting(filter, setting); if (groupMatched || settingMatches.length > 0) { settings.push(setting); } @@ -61,9 +60,9 @@ export abstract class AbstractSettingsModel extends Disposable { } if (settings.length) { sections.push({ - description: section.description, + title: section.title, settings, - descriptionRange: section.descriptionRange + titleRange: section.titleRange }); } } @@ -88,7 +87,113 @@ export abstract class AbstractSettingsModel extends Disposable { return null; } - protected abstract _findMatchesInSetting(searchString: string, searchRegex: RegExp, setting: ISetting): IRange[]; + public getSetting(key: string): ISetting { + for (const group of this.settingsGroups) { + for (const section of group.sections) { + for (const setting of section.settings) { + if (key === setting.key) { + return setting; + } + } + } + } + return null; + } + + private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] { + const registry: { [qualifiedKey: string]: IJSONSchema } = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const schema: IJSONSchema = registry[setting.key]; + + let words = searchString.split(' '); + let descriptionMatchingWords: Map = new Map(); + let keyMatchingWords: Map = new Map(); + let valueMatchingWords: Map = new Map(); + const settingKeyAsWords: string = setting.key.split('.').join(' '); + + for (const word of words) { + for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { + const descriptionMatches = matchesWords(word, setting.description[lineIndex], true); + if (descriptionMatches) { + descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex))); + } + } + + const keyMatches = or(matchesWords, matchesCamelCase)(word, settingKeyAsWords); + if (keyMatches) { + keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match))); + } + + if (schema.type === 'string' || schema.enum) { + const valueMatches = matchesContiguousSubString(word, setting.value); + if (valueMatches) { + valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match))); + } else if (schema.enum && schema.enum.some(enumValue => !!matchesContiguousSubString(word, enumValue))) { + valueMatchingWords.set(word, []); + } + } + } + + const descriptionRanges: IRange[] = []; + for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { + const matches = or(matchesContiguousSubString)(searchString, setting.description[lineIndex]) || []; + descriptionRanges.push(...matches.map(match => this.toDescriptionRange(setting, match, lineIndex))); + } + if (descriptionRanges.length === 0) { + descriptionRanges.push(...this.getRangesForWords(words, descriptionMatchingWords, [keyMatchingWords, valueMatchingWords])); + } + + const keyMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.key); + const keyRanges: IRange[] = keyMatches ? keyMatches.map(match => this.toKeyRange(setting, match)) : this.getRangesForWords(words, keyMatchingWords, [descriptionMatchingWords, valueMatchingWords]); + + let valueRanges: IRange[] = []; + if (typeof setting.value === 'string') { + const valueMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.value); + valueRanges = valueMatches ? valueMatches.map(match => this.toValueRange(setting, match)) : this.getRangesForWords(words, valueMatchingWords, [keyMatchingWords, descriptionMatchingWords]); + } + + return [...descriptionRanges, ...keyRanges, ...valueRanges]; + } + + private getRangesForWords(words: string[], from: Map, others: Map[]): IRange[] { + const result: IRange[] = []; + for (const word of words) { + const ranges = from.get(word); + if (ranges) { + result.push(...ranges); + } else if (others.every(o => !o.has(word))) { + return []; + } + } + return result; + } + + private toKeyRange(setting: ISetting, match: IMatch): IRange { + return { + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn + match.start, + endLineNumber: setting.keyRange.startLineNumber, + endColumn: setting.keyRange.startColumn + match.end + }; + } + + private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange { + return { + startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber + lineIndex, + startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start, + endLineNumber: setting.descriptionRanges[lineIndex].startLineNumber + lineIndex, + endColumn: setting.descriptionRanges[lineIndex].startColumn + match.end + }; + } + + private toValueRange(setting: ISetting, match: IMatch): IRange { + return { + startLineNumber: setting.valueRange.startLineNumber, + startColumn: setting.valueRange.startColumn + match.start + 1, + endLineNumber: setting.valueRange.startLineNumber, + endColumn: setting.valueRange.startColumn + match.end + 1 + }; + } + public abstract settingsGroups: ISettingsGroup[]; } @@ -183,7 +288,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti // setting started let settingStartPosition = model.getPositionAt(offset); settings.push({ - description: '', + description: [], key: name, keyRange: { startLineNumber: settingStartPosition.lineNumber, @@ -199,7 +304,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti }, value: null, valueRange: null, - descriptionRange: null, + descriptionRanges: null, }); } }, @@ -248,6 +353,10 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti }, onLiteralValue: onValue, onError: (error) => { + const setting = settings[settings.length - 1]; + if (!setting.range || !setting.keyRange || !setting.valueRange) { + settings.pop(); + } } }; visit(model.getValue(), visitor); @@ -262,20 +371,6 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti range }] : []; } - - protected _findMatchesInSetting(searchString: string, searchRegex: RegExp, setting: ISetting): IRange[] { - const result: IRange[] = []; - for (let lineNumber = setting.range.startLineNumber; lineNumber <= setting.range.endLineNumber; lineNumber++) { - result.push(...this._findMatchesInLine(searchString, lineNumber)); - } - return result; - } - - private _findMatchesInLine(searchString: string, lineNumber: number): IRange[] { - return this.model.findMatches(searchString, { - startLineNumber: lineNumber, startColumn: this.model.getLineMinColumn(lineNumber), endLineNumber: lineNumber, endColumn: this.model.getLineMaxColumn(lineNumber), - }, false, false, false); - } } export class DefaultSettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel { @@ -383,7 +478,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements result.push(settingsGroup); } } else { - settingsGroup.sections[settingsGroup.sections.length - 1].description = config.title; + settingsGroup.sections[settingsGroup.sections.length - 1].title = config.title; } } if (config.properties) { @@ -394,8 +489,8 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements const configurationSettings: ISetting[] = Object.keys(config.properties).map((key) => { const prop = config.properties[key]; const value = prop.default; - const description = prop.description || ''; - return { key, value, description, range: null, keyRange: null, valueRange: null, descriptionRange: null }; + const description = (prop.description || '').split('\n'); + return { key, value, description, range: null, keyRange: null, valueRange: null, descriptionRanges: [] }; }); settingsGroup.sections[settingsGroup.sections.length - 1].settings.push(...configurationSettings); } @@ -449,16 +544,20 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements this._contentByLines.push(''); let groupStart = this._contentByLines.length + 1; for (const section of group.sections) { - if (section.description) { + if (section.title) { let sectionTitleStart = this._contentByLines.length + 1; - this.addDescription(section.description, this.indent, this._contentByLines); - section.descriptionRange = { startLineNumber: sectionTitleStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }; + this.addDescription([section.title], this.indent, this._contentByLines); + section.titleRange = { startLineNumber: sectionTitleStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }; } for (const setting of section.settings) { const settingStart = this._contentByLines.length + 1; - this.addDescription(setting.description, this.indent, this._contentByLines); - setting.descriptionRange = { startLineNumber: settingStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }; + setting.descriptionRanges = []; + const descriptionPreValue = this.indent + '// '; + for (const line of setting.description) { + this._contentByLines.push(descriptionPreValue + line); + setting.descriptionRanges.push({ startLineNumber: this._contentByLines.length, startColumn: this._contentByLines[this._contentByLines.length - 1].indexOf(line) + 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }); + } let preValueConent = this.indent; const keyString = JSON.stringify(setting.key); @@ -489,41 +588,13 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return lastSetting; } - private addDescription(description: string, indent: string, result: string[]) { - const multiLines = description.split('\n'); - for (const line of multiLines) { + private addDescription(description: string[], indent: string, result: string[]) { + for (const line of description) { result.push(indent + '// ' + line); } } - protected _findMatchesInSetting(searchString: string, searchRegex: RegExp, setting: ISetting): IRange[] { - const result: IRange[] = [...this._findMatchesInDescription(searchString, setting)]; - for (let lineNumber = setting.valueRange.startLineNumber; lineNumber <= setting.valueRange.endLineNumber; lineNumber++) { - result.push(...this._findMatchesInLine(searchRegex, lineNumber)); - } - return result; - } - - private _findMatchesInDescription(searchString: string, setting: ISetting): IRange[] { - const result: IRange[] = []; - for (let lineNumber = setting.descriptionRange.startLineNumber; lineNumber <= setting.descriptionRange.endLineNumber; lineNumber++) { - const content = this._contentByLines[lineNumber - 1]; - const matches: IMatch[] = AbstractSettingsModel._fuzzyFilter(searchString, content); - if (matches) { - result.push(...matches.map(match => { - return { - startLineNumber: lineNumber, - startColumn: match.start + 1, - endLineNumber: lineNumber, - endColumn: match.end + 1 - }; - })); - } - } - return result; - } - - private _findMatchesInLine(searchRegex: RegExp, lineNumber: number): IRange[] { + /*private _findMatchesInLine(searchRegex: RegExp, lineNumber: number): IRange[] { const result: IRange[] = []; const text = this._contentByLines[lineNumber - 1]; var m: RegExpExecArray; @@ -541,7 +612,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements } } while (m); return result; - } + }*/ } export class DefaultKeybindingsEditorModel implements IKeybindingsEditorModel { diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts index ccfec2794a6..9aa26fe7ec1 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts @@ -251,7 +251,7 @@ export class CommandsHandler extends QuickOpenHandler { // Workbench Actions (if prefix asks for all commands) let workbenchEntries: CommandEntry[] = []; if (this.includeWorkbenchCommands()) { - const workbenchActions = (Registry.as(ActionExtensions.WorkbenchActions)).getWorkbenchActions(); + const workbenchActions = Registry.as(ActionExtensions.WorkbenchActions).getWorkbenchActions(); workbenchEntries = this.actionDescriptorsToEntries(workbenchActions, searchValue); } diff --git a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts index 501d38dedd4..18eabe4b04d 100644 --- a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts @@ -46,7 +46,7 @@ class OutlineModel extends QuickOpenModel { this.outline = outline; } - public dofilter(searchValue: string): void { + public applyFilter(searchValue: string): void { // Normalize search let normalizedSearchValue = searchValue; @@ -389,10 +389,10 @@ export class GotoSymbolHandler extends QuickOpenHandler { } // Resolve Outline Model - return this.getActiveOutline().then((outline) => { + return this.getActiveOutline().then(outline => { // Filter by search - outline.dofilter(searchValue); + outline.applyFilter(searchValue); return outline; }); @@ -548,7 +548,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { public clearDecorations(): void { if (this.rangeHighlightDecorationId) { - this.editorService.getVisibleEditors().forEach((editor) => { + this.editorService.getVisibleEditors().forEach(editor => { if (editor.position === this.rangeHighlightDecorationId.position) { const editorControl = editor.getControl(); editorControl.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { diff --git a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts index ed7e4dc3bcc..154b5eb08c0 100644 --- a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts +++ b/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts @@ -16,6 +16,7 @@ import { GotoSymbolAction, GOTO_SYMBOL_PREFIX, SCOPE_PREFIX } from 'vs/workbench import { ShowAllCommandsAction, ALL_COMMANDS_PREFIX } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; import { GotoLineAction, GOTO_LINE_PREFIX } from 'vs/workbench/parts/quickopen/browser/gotoLineHandler'; import { HELP_PREFIX } from 'vs/workbench/parts/quickopen/browser/helpHandler'; +import { VIEW_PICKER_PREFIX, OpenViewPickerAction, QuickOpenViewPickerAction } from 'vs/workbench/parts/quickopen/browser/viewPickerHandler'; // Register Actions let registry = Registry.as(ActionExtensions.WorkbenchActions); @@ -33,9 +34,14 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(GotoSymbolAction, Goto primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O }), 'Go to Symbol...'); +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'Open View'); +registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_Q }, linux: { primary: null } +}), 'Quick Open View'); + // Register Quick Open Handler -(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( new QuickOpenHandlerDescriptor( 'vs/workbench/parts/quickopen/browser/commandsHandler', 'CommandsHandler', @@ -44,7 +50,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(GotoSymbolAction, Goto ) ); -(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( new QuickOpenHandlerDescriptor( 'vs/workbench/parts/quickopen/browser/gotoLineHandler', 'GotoLineHandler', @@ -59,7 +65,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(GotoSymbolAction, Goto ) ); -(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( new QuickOpenHandlerDescriptor( 'vs/workbench/parts/quickopen/browser/gotoSymbolHandler', 'GotoSymbolHandler', @@ -79,11 +85,26 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(GotoSymbolAction, Goto ) ); -(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( new QuickOpenHandlerDescriptor( 'vs/workbench/parts/quickopen/browser/helpHandler', 'HelpHandler', HELP_PREFIX, nls.localize('helpDescription', "Show Help") ) +); + +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( + new QuickOpenHandlerDescriptor( + 'vs/workbench/parts/quickopen/browser/viewPickerHandler', + 'ViewPickerHandler', + VIEW_PICKER_PREFIX, + [ + { + prefix: VIEW_PICKER_PREFIX, + needsEditor: false, + description: nls.localize('viewPickerDescription', "Open View") + } + ] + ) ); \ No newline at end of file diff --git a/src/vs/workbench/parts/viewpicker/browser/viewPickerHandler.ts b/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts similarity index 100% rename from src/vs/workbench/parts/viewpicker/browser/viewPickerHandler.ts rename to src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index b4f7cb01317..11a0c709922 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -42,18 +42,15 @@ .search-viewlet .search-widget .replace-container { margin-top: 6px; position: relative; + display: inline-flex; + height: 25px; } .search-viewlet .search-widget .replace-container.disabled { display: none; } -.search-viewlet .search-widget .replace-container > * { - display: inline-block; -} - .search-viewlet .search-widget .replace-container .monaco-action-bar { - position: absolute; margin-left: 3px; } diff --git a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts index 2f54b603ac2..541a5d29ef8 100644 --- a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts +++ b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts @@ -19,9 +19,7 @@ import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; import { FileEntry, OpenFileHandler, FileQuickOpenModel } from 'vs/workbench/parts/search/browser/openFileHandler'; -/* tslint:disable:no-unused-variable */ import * as openSymbolHandler from 'vs/workbench/parts/search/browser/openSymbolHandler'; -/* tslint:enable:no-unused-variable */ import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ISearchStats, ICachedSearchStats, IUncachedSearchStats } from 'vs/platform/search/common/search'; diff --git a/src/vs/workbench/parts/search/browser/replaceService.ts b/src/vs/workbench/parts/search/browser/replaceService.ts index c64faa6cde3..f396e9f023d 100644 --- a/src/vs/workbench/parts/search/browser/replaceService.ts +++ b/src/vs/workbench/parts/search/browser/replaceService.ts @@ -97,7 +97,7 @@ export class ReplaceService implements IReplaceService { public replace(match: FileMatchOrMatch, progress?: IProgressRunner, resource?: URI): TPromise public replace(arg: any, progress: IProgressRunner = null, resource: URI = null): TPromise { - let bulkEdit: BulkEdit = createBulkEdit(this.fileService, this.textModelResolverService, null); + let bulkEdit: BulkEdit = createBulkEdit(this.textModelResolverService, null, this.fileService); bulkEdit.progress(progress); if (arg instanceof Match) { diff --git a/src/vs/workbench/parts/search/browser/search.contribution.ts b/src/vs/workbench/parts/search/browser/search.contribution.ts index 49de2d67ff0..08e9598402a 100644 --- a/src/vs/workbench/parts/search/browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/browser/search.contribution.ts @@ -126,6 +126,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FocusAct registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FindInFilesAction, Constants.FindInFilesActionId, nls.localize('findInFiles', "Find in Files"), { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchInputBoxFocussedKey.toNegated()), 'Find in Files'); +registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FocusNextSearchResultAction, searchActions.FocusNextSearchResultAction.ID, searchActions.FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }), ''); +registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FocusPreviousSearchResultAction, searchActions.FocusPreviousSearchResultAction.ID, searchActions.FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }), ''); + registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.ReplaceInFilesAction, searchActions.ReplaceInFilesAction.ID, searchActions.ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files'); registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.CloseReplaceAction, Constants.CloseReplaceWidgetActionId, '', { primary: KeyCode.Escape }, ContextKeyExpr.and(Constants.SearchViewletVisibleKey, Constants.ReplaceInputBoxFocussedKey)), ''); diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 66fed1dbee6..c514ad5bab9 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -266,6 +266,36 @@ export class ClearSearchResultsAction extends Action { } } +export class FocusNextSearchResultAction extends Action { + public static ID = 'search.action.focusNextSearchResult'; + public static LABEL = nls.localize('FocusNextSearchResult.label', "Focus next search result"); + + constructor(id: string, label: string, @IViewletService private viewletService: IViewletService) { + super(id, label); + } + + public run(): TPromise { + return this.viewletService.openViewlet(Constants.VIEWLET_ID).then((searchViewlet: SearchViewlet) => { + searchViewlet.selectNextResult(); + }); + } +} + +export class FocusPreviousSearchResultAction extends Action { + public static ID = 'search.action.focusPreviousSearchResult'; + public static LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus previous search result"); + + constructor(id: string, label: string, @IViewletService private viewletService: IViewletService) { + super(id, label); + } + + public run(): TPromise { + return this.viewletService.openViewlet(Constants.VIEWLET_ID).then((searchViewlet: SearchViewlet) => { + searchViewlet.selectPreviousResult(); + }); + } +} + export abstract class AbstractSearchAndReplaceAction extends Action { /** diff --git a/src/vs/workbench/parts/search/browser/searchResultsView.ts b/src/vs/workbench/parts/search/browser/searchResultsView.ts index 10b3a248a12..f08db80855d 100644 --- a/src/vs/workbench/parts/search/browser/searchResultsView.ts +++ b/src/vs/workbench/parts/search/browser/searchResultsView.ts @@ -282,7 +282,18 @@ export class SearchController extends DefaultController { this.viewlet.moveFocusFromResults(); return true; } - return super.onUp(tree, event); + + const result = super.onUp(tree, event); + let focus = tree.getFocus(); + this.selectOnScroll(tree, focus, event); + return result; + } + + protected onDown(tree: ITree, event: IKeyboardEvent): boolean { + const result = super.onDown(tree, event); + let focus = tree.getFocus(); + this.selectOnScroll(tree, focus, event); + return result; } protected onSpace(tree: ITree, event: IKeyboardEvent): boolean { @@ -292,6 +303,14 @@ export class SearchController extends DefaultController { } super.onSpace(tree, event); } + + private selectOnScroll(tree: ITree, focus: any, event: IKeyboardEvent): void { + if (focus instanceof Match) { + this.onEnter(tree, event); + } else { + tree.setSelection([focus]); + } + } } export class SearchFilter implements IFilter { diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 255eae24ba1..1fcd97fd773 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -410,7 +410,7 @@ export class SearchViewlet extends Viewlet { } let sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)); - let focusEditor = (keyboard && (originalEvent).keyCode === KeyCode.Enter) || doubleClick; + let focusEditor = (keyboard && (originalEvent).keyCode === KeyCode.Enter) || doubleClick || event.payload.focusEditor; if (element instanceof Match) { let selectedMatch: Match = element; @@ -449,6 +449,60 @@ export class SearchViewlet extends Viewlet { } } + public selectNextResult(): void { + const eventPayload = { focusEditor: true }; + const [selected]: FileMatchOrMatch[] = this.tree.getSelection(); + const navigator = this.tree.getNavigator(selected, /*subTreeOnly=*/false); + let next = navigator.next(); + + if (!next) { + return; + } + + // Expand and go past FileMatch nodes + if (!(next instanceof Match)) { + if (!this.tree.isExpanded(next)) { + this.tree.expand(next); + } + + // Select the FileMatch's first child + next = navigator.next(); + } + + // Reveal the newly selected element + this.tree.setFocus(next, eventPayload); + this.tree.setSelection([next], eventPayload); + this.tree.reveal(next); + } + + public selectPreviousResult(): void { + const eventPayload = { focusEditor: true }; + const [selected]: FileMatchOrMatch[] = this.tree.getSelection(); + const navigator = this.tree.getNavigator(selected, /*subTreeOnly=*/false); + + let prev = navigator.previous(); + if (!prev) { + return; + } + + // Expand and go past FileMatch nodes + if (!(prev instanceof Match)) { + prev = navigator.previous(); + if (!(prev instanceof Match)) { + // There is a second non-Match result, which must be a collapsed FileMatch. + // Expand it then select its last child. + navigator.next(); + this.tree.expand(prev); + prev = navigator.previous(); + } + } + + // Reveal the newly selected element + this.tree.setFocus(prev, eventPayload); + this.tree.setSelection([prev], eventPayload); + this.tree.reveal(prev); + } + public setVisible(visible: boolean): TPromise { let promise: TPromise; this.viewletVisible.set(visible); diff --git a/src/vs/workbench/parts/search/common/constants.ts b/src/vs/workbench/parts/search/common/constants.ts index 8b3a46f966d..42daf7da501 100644 --- a/src/vs/workbench/parts/search/common/constants.ts +++ b/src/vs/workbench/parts/search/common/constants.ts @@ -9,6 +9,7 @@ export const VIEWLET_ID = 'workbench.view.search'; export const FindInFilesActionId = 'workbench.action.findInFiles'; export const FocusActiveEditorActionId = 'search.action.focusActiveEditor'; + export const ToggleCaseSensitiveActionId = 'toggleSearchCaseSensitive'; export const ToggleWholeWordActionId = 'toggleSearchWholeWord'; export const ToggleRegexActionId = 'toggleSearchRegex'; diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index bca28da2a8b..e2ba6cde2ed 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as timer from 'vs/base/common/timer'; import paths = require('vs/base/common/paths'); import objects = require('vs/base/common/objects'); import strings = require('vs/base/common/strings'); @@ -503,7 +502,7 @@ export class SearchModel extends Disposable { private currentRequest: PPromise; - constructor( @ISearchService private searchService, @ITelemetryService private telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService) { + constructor( @ISearchService private searchService: ISearchService, @ITelemetryService private telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService) { super(); this._searchResult = this.instantiationService.createInstance(SearchResult, this); } @@ -544,14 +543,12 @@ export class SearchModel extends Disposable { this._searchResult.query = this._searchQuery.contentPattern; this._replacePattern = new ReplacePattern(this._replaceString, this._searchQuery.contentPattern); - const timerEvent = timer.start(timer.Topic.WORKBENCH, 'Search'); this.currentRequest = this.searchService.search(this._searchQuery); const onDone = fromPromise(this.currentRequest); const onDoneStopwatch = stopwatch(onDone); const start = Date.now(); - onDone(() => timerEvent.stop()); onDoneStopwatch(duration => this.telemetryService.publicLog('searchResultsFinished', { duration })); const progressEmitter = new Emitter(); diff --git a/src/vs/workbench/parts/search/test/common/searchModel.test.ts b/src/vs/workbench/parts/search/test/common/searchModel.test.ts index 3a5417c1ff8..ca52028f261 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchModel.test.ts @@ -9,7 +9,6 @@ import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { DeferredPPromise } from 'vs/base/test/common/utils'; import { PPromise } from 'vs/base/common/winjs.base'; -import { nullEvent } from 'vs/base/common/timer'; import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; import URI from 'vs/base/common/uri'; import { IFileMatch, ILineMatch, ISearchService, ISearchComplete, ISearchProgressItem, IUncachedSearchStats } from 'vs/platform/search/common/search'; @@ -20,6 +19,27 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +const nullEvent = new class { + + public id: number; + public topic: string; + public name: string; + public description: string; + public data: any; + + public startTime: Date; + public stopTime: Date; + + public stop(): void { + return; + } + + public timeTaken(): number { + return -1; + } +}; + + suite('SearchModel', () => { let instantiationService: TestInstantiationService; @@ -292,4 +312,4 @@ suite('SearchModel', () => { return instantiationService.createInstance(ModelServiceImpl); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts index ef9c163a4ae..9c05c47a36c 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts @@ -40,7 +40,7 @@ class OpenSnippetsAction extends actions.Action { } private openFile(filePath: string): winjs.TPromise { - return this.windowsService.windowOpen([filePath]); + return this.windowsService.openWindow([filePath], { forceReuseWindow: true }); } public run(): winjs.Promise { diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts b/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts index 1dce853f6a9..3d95492bca2 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts @@ -14,7 +14,6 @@ import { mkdirp, fileExists, readdir } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import lifecycle = require('vs/base/common/lifecycle'); import { readAndRegisterSnippets } from 'vs/editor/node/textMate/TMSnippets'; -import { IFileService } from 'vs/platform/files/common/files'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { watch, FSWatcher } from 'fs'; @@ -28,7 +27,6 @@ export class SnippetsTracker implements workbenchExt.IWorkbenchContribution { private fileWatchDelayer: async.ThrottledDelayer; constructor( - @IFileService private fileService: IFileService, @ILifecycleService private lifecycleService: ILifecycleService, @IEnvironmentService environmentService: IEnvironmentService ) { 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 f27caed5b94..f6c97a0c0b4 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -181,17 +181,17 @@ /* Base selection colors */ -.monaco-workbench .panel.integrated-terminal .xterm *::selection { +.monaco-workbench .panel.integrated-terminal .xterm ::selection { color: #FFF; background-color: rgba(51, 51, 51, 0.996); } -.vs-dark .monaco-workbench .panel.integrated-terminal .xterm *::selection { +.vs-dark .monaco-workbench .panel.integrated-terminal .xterm ::selection { color: #1e1e1e; background-color: rgba(204, 204, 204, 0.996); } -.hc-black .monaco-workbench .panel.integrated-terminal .xterm *::selection { +.hc-black .monaco-workbench .panel.integrated-terminal .xterm ::selection { color: #000; background-color: rgba(255, 255, 255, 0.996); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index c0949423672..4aa82872e94 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -227,7 +227,7 @@ export class SwitchTerminalInstanceAction extends Action { } public run(item?: string): TPromise { - if (!item) { + if (!item || !item.split) { return TPromise.as(null); } const selectedTerminalIndex = parseInt(item.split(':')[0], 10) - 1; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index 113024a23db..7d05f7e29aa 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IConfiguration, DefaultConfig } from 'vs/editor/common/config/defaultConfig'; +import { IConfiguration as IEditorConfiguration, DefaultConfig } from 'vs/editor/common/config/defaultConfig'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShell } from 'vs/workbench/parts/terminal/common/terminal'; import { Platform } from 'vs/base/common/platform'; @@ -91,16 +91,16 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { this._charMeasureElement = document.createElement('div'); this.panelContainer.appendChild(this._charMeasureElement); } - let style = this._charMeasureElement.style; + const style = this._charMeasureElement.style; style.display = 'block'; style.fontFamily = fontFamily; style.fontSize = fontSize + 'px'; - style.height = Math.floor(lineHeight * fontSize) + 'px'; + style.lineHeight = lineHeight.toString(10); this._charMeasureElement.innerText = 'X'; - let rect = this._charMeasureElement.getBoundingClientRect(); + const rect = this._charMeasureElement.getBoundingClientRect(); style.display = 'none'; - let charWidth = Math.ceil(rect.width); - let charHeight = Math.ceil(rect.height); + const charWidth = rect.width; + const charHeight = rect.height; return { fontFamily, fontSize: fontSize + 'px', @@ -115,37 +115,46 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { * terminal.integrated.fontSize, terminal.integrated.lineHeight configuration properties */ public getFont(): ITerminalFont { - let terminalConfig = this._configurationService.getConfiguration().terminal.integrated; - let editorConfig = this._configurationService.getConfiguration(); + const config = this._configurationService.getConfiguration(); + const editorConfig = (config).editor; + const terminalConfig = (config).terminal.integrated; - let fontFamily = terminalConfig.fontFamily || editorConfig.editor.fontFamily; + const fontFamily = terminalConfig.fontFamily || editorConfig.fontFamily; let fontSize = this._toInteger(terminalConfig.fontSize, 0); if (fontSize <= 0) { fontSize = DefaultConfig.editor.fontSize; } let lineHeight = terminalConfig.lineHeight <= 0 ? DEFAULT_LINE_HEIGHT : terminalConfig.lineHeight; + if (!lineHeight) { + lineHeight = DEFAULT_LINE_HEIGHT; + } return this._measureFont(fontFamily, fontSize, lineHeight); } public getFontLigaturesEnabled(): boolean { - let terminalConfig = this._configurationService.getConfiguration().terminal.integrated; - return terminalConfig.fontLigatures; + const terminalConfig = this._configurationService.getConfiguration(); + return terminalConfig.terminal.integrated.fontLigatures; } public getCursorBlink(): boolean { - let terminalConfig = this._configurationService.getConfiguration().terminal.integrated; - return terminalConfig.cursorBlinking; + const terminalConfig = this._configurationService.getConfiguration(); + return terminalConfig.terminal.integrated.cursorBlinking; } public getRightClickCopyPaste(): boolean { - let config = this._configurationService.getConfiguration(); + const config = this._configurationService.getConfiguration(); return config.terminal.integrated.rightClickCopyPaste; } + public getCommandsToSkipShell(): string[] { + const config = this._configurationService.getConfiguration(); + return config.terminal.integrated.commandsToSkipShell; + } + public getShell(): IShell { - let config = this._configurationService.getConfiguration(); - let shell: IShell = { + const config = this._configurationService.getConfiguration(); + const shell: IShell = { executable: '', args: [] }; @@ -190,9 +199,4 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { } return r; } - - public getCommandsToSkipShell(): string[] { - let config = this._configurationService.getConfiguration(); - return config.terminal.integrated.commandsToSkipShell; - } } \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 6121cc1c58c..f2b76b3f41c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -25,9 +25,10 @@ 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'; +/** The amount of time to consider terminal errors to be related to the launch */ +const LAUNCHING_DURATION = 500; + export class TerminalInstance implements ITerminalInstance { - /** The amount of time to consider terminal errors to be related to the launch */ - private static readonly LAUNCHING_DURATION = 500; private static readonly EOL_REGEX = /\r?\n/g; private static _idCounter = 1; @@ -155,8 +156,8 @@ export class TerminalInstance implements ITerminalInstance { }, 0); }); - let xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); - let focusTrap: HTMLElement = document.createElement('div'); + const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); + const focusTrap: HTMLElement = document.createElement('div'); focusTrap.setAttribute('tabindex', '0'); DOM.addClass(focusTrap, 'focus-trap'); focusTrap.addEventListener('focus', function (event: FocusEvent) { @@ -164,7 +165,7 @@ export class TerminalInstance implements ITerminalInstance { while (!DOM.hasClass(currentElement, 'part')) { currentElement = currentElement.parentElement; } - let hidePanelElement = currentElement.querySelector('.hide-panel-action'); + const hidePanelElement = currentElement.querySelector('.hide-panel-action'); hidePanelElement.focus(); }); xtermHelper.insertBefore(focusTrap, this._xterm.textarea); @@ -239,7 +240,7 @@ export class TerminalInstance implements ITerminalInstance { if (!this._xterm) { return; } - let text = window.getSelection().toString(); + const text = window.getSelection().toString(); if (!text || force) { this._xterm.focus(); } @@ -306,7 +307,7 @@ export class TerminalInstance implements ITerminalInstance { } protected _getCwd(workspace: IWorkspace, ignoreCustomCwd: boolean): string { - let cwd; + let cwd: string; // TODO: Handle non-existent customCwd if (!ignoreCustomCwd) { @@ -330,11 +331,11 @@ export class TerminalInstance implements ITerminalInstance { } protected _createProcess(workspace: IWorkspace, name: string, shell: IShell) { - let locale = this._configHelper.isSetLocaleVariables() ? platform.locale : undefined; + const locale = this._configHelper.isSetLocaleVariables() ? platform.locale : undefined; if (!shell.executable) { shell = this._configHelper.getShell(); } - let env = TerminalInstance.createTerminalEnv(process.env, shell, this._getCwd(workspace, shell.ignoreCustomCwd), locale); + const env = TerminalInstance.createTerminalEnv(process.env, shell, this._getCwd(workspace, shell.ignoreCustomCwd), locale); this._title = name ? name : ''; this._process = cp.fork('./terminalProcess', [], { env: env, @@ -355,7 +356,7 @@ export class TerminalInstance implements ITerminalInstance { this._onProcessIdReady.fire(this); } }); - this._process.on('exit', (exitCode) => { + this._process.on('exit', (exitCode: number) => { // Prevent dispose functions being triggered multiple times if (!this._isExiting) { this.dispose(); @@ -371,13 +372,13 @@ export class TerminalInstance implements ITerminalInstance { }); setTimeout(() => { this._isLaunching = false; - }, TerminalInstance.LAUNCHING_DURATION); + }, LAUNCHING_DURATION); } // TODO: This should be private/protected // TODO: locale should not be optional public static createTerminalEnv(parentEnv: IStringDictionary, shell: IShell, cwd: string, locale?: string): IStringDictionary { - let env = TerminalInstance._cloneEnv(parentEnv); + const env = TerminalInstance._cloneEnv(parentEnv); env['PTYPID'] = process.pid.toString(); env['PTYSHELL'] = shell.executable; if (shell.args) { @@ -401,7 +402,7 @@ export class TerminalInstance implements ITerminalInstance { } private static _cloneEnv(env: IStringDictionary): IStringDictionary { - let newEnv: IStringDictionary = Object.create(null); + const newEnv: IStringDictionary = Object.create(null); Object.keys(env).forEach((key) => { newEnv[key] = env[key]; }); @@ -441,7 +442,7 @@ export class TerminalInstance implements ITerminalInstance { } public layout(dimension: { width: number, height: number }): void { - let font = this._configHelper.getFont(); + const font = this._configHelper.getFont(); if (!font || !font.charWidth || !font.charHeight) { return; } @@ -454,10 +455,12 @@ export class TerminalInstance implements ITerminalInstance { // Upstream issue: https://github.com/sourcelair/xterm.js/issues/291 this._xterm.emit('scroll', this._xterm.ydisp); } - let leftPadding = parseInt(getComputedStyle(document.querySelector('.terminal-outer-container')).paddingLeft.split('px')[0], 10); - let innerWidth = dimension.width - leftPadding; - let cols = Math.floor(innerWidth / font.charWidth); - let rows = Math.floor(dimension.height / font.charHeight); + const padding = parseInt(getComputedStyle(document.querySelector('.terminal-outer-container')).paddingLeft.split('px')[0], 10); + // Use left padding as right padding, right padding is not defined in CSS just in case + // xterm.js causes an unexpected overflow. + const innerWidth = dimension.width - padding * 2; + const cols = Math.floor(innerWidth / font.charWidth); + const rows = Math.floor(dimension.height / font.charHeight); if (this._xterm) { this._xterm.resize(cols, rows); this._xterm.element.style.width = innerWidth + 'px'; diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index 40c3a86214e..85f19d8c247 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -16,10 +16,7 @@ import { IThemeService } from 'vs/workbench/services/themes/common/themeService' import { ReleaseNotesInput } from './releaseNotesInput'; import { EditorOptions } from 'vs/workbench/common/editor'; import WebView from 'vs/workbench/parts/html/browser/webview'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IRequestService } from 'vs/platform/request/node/request'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IModeService } from 'vs/editor/common/services/modeService'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; @@ -46,10 +43,7 @@ export class ReleaseNotesEditor extends BaseEditor { constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService private themeService: IThemeService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IRequestService private requestService: IRequestService, @IOpenerService private openerService: IOpenerService, - @IKeybindingService private keybindingService: IKeybindingService, @IModeService private modeService: IModeService ) { super(ReleaseNotesEditor.ID, telemetryService); diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index b2471615aee..c537d235a44 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -119,13 +119,12 @@ export class OpenLatestReleaseNotesInBrowserAction extends Action { export abstract class AbstractShowReleaseNotesAction extends Action { constructor( - id, - label, + id: string, + label: string, private returnValue: boolean, private version: string, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IInstantiationService private instantiationService: IInstantiationService, - @IOpenerService private openerService: IOpenerService + @IInstantiationService private instantiationService: IInstantiationService ) { super(id, label, null, true); } @@ -153,10 +152,9 @@ export class ShowReleaseNotesAction extends AbstractShowReleaseNotesAction { returnValue: boolean, version: string, @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IInstantiationService instantiationService: IInstantiationService, - @IOpenerService openerService: IOpenerService + @IInstantiationService instantiationService: IInstantiationService ) { - super('update.showReleaseNotes', nls.localize('releaseNotes', "Release Notes"), returnValue, version, editorService, instantiationService, openerService); + super('update.showReleaseNotes', nls.localize('releaseNotes', "Release Notes"), returnValue, version, editorService, instantiationService); } } @@ -169,10 +167,9 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio id = ShowCurrentReleaseNotesAction.ID, label = ShowCurrentReleaseNotesAction.LABEL, @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IInstantiationService instantiationService: IInstantiationService, - @IOpenerService openerService: IOpenerService + @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, true, pkg.version, editorService, instantiationService, openerService); + super(id, label, true, pkg.version, editorService, instantiationService); } } diff --git a/src/vs/workbench/parts/viewpicker/browser/viewpicker.contribution.ts b/src/vs/workbench/parts/viewpicker/browser/viewpicker.contribution.ts deleted file mode 100644 index 9ab715cc1a1..00000000000 --- a/src/vs/workbench/parts/viewpicker/browser/viewpicker.contribution.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 { Registry } from 'vs/platform/platform'; -import nls = require('vs/nls'); -import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions } from 'vs/workbench/browser/quickopen'; -import { VIEW_PICKER_PREFIX, OpenViewPickerAction, QuickOpenViewPickerAction } from 'vs/workbench/parts/viewpicker/browser/viewPickerHandler'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( - 'vs/workbench/parts/viewpicker/browser/viewPickerHandler', - 'ViewPickerHandler', - VIEW_PICKER_PREFIX, - [ - { - prefix: VIEW_PICKER_PREFIX, - needsEditor: false, - description: nls.localize('viewPickerDescription', "Open View") - } - ] - ) -); - -const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'Open View'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_Q }, linux: { primary: null } }), 'Quick Open View'); diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index f028e5e8491..b1ef6c8fa37 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -187,7 +187,7 @@ export class WorkspaceConfigurationService implements IWorkspaceConfigurationSer this.cachedWorkspaceConfig = workspaceConfig; // Cache keys - const workspaceConfigKeys = []; + const workspaceConfigKeys: string[] = []; Object.keys(workspaceConfigFiles).forEach(path => { if (path === WORKSPACE_CONFIG_DEFAULT_PATH) { workspaceConfigKeys.push(...Object.keys(workspaceConfigFiles[path].raw)); diff --git a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts index aa4fd21fe51..193e17db95b 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -25,7 +25,6 @@ import URI from 'vs/base/common/uri'; import { FileService } from 'vs/workbench/services/files/node/fileService'; import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; import { ConfigurationTarget, IConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -56,7 +55,6 @@ class TestDirtyTextFileService extends TestTextFileService { @IConfigurationService configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IEditorGroupService editorGroupService: IEditorGroupService, @IFileService fileService: IFileService, @IUntitledEditorService untitledEditorService: IUntitledEditorService, @IInstantiationService instantiationService: IInstantiationService, @@ -64,7 +62,7 @@ class TestDirtyTextFileService extends TestTextFileService { @IBackupFileService backupFileService: IBackupFileService, @IWindowsService windowsService: IWindowsService ) { - super(lifecycleService, contextService, configurationService, telemetryService, editorService, editorGroupService, fileService, untitledEditorService, instantiationService, messageService, backupFileService, windowsService); + super(lifecycleService, contextService, configurationService, telemetryService, editorService, fileService, untitledEditorService, instantiationService, messageService, backupFileService, windowsService); } public isDirty(resource?: URI): boolean { diff --git a/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts index f214ca62645..71a230c677c 100644 --- a/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts @@ -221,7 +221,7 @@ export class ConfigurationResolverService implements IConfigurationResolverServi const factory: { (): TPromise }[] = Object.keys(interactiveVariablesToSubstitutes).map(interactiveVariable => { return () => { - let commandId = null; + let commandId: string = null; commandId = interactiveVariablesMap ? interactiveVariablesMap[interactiveVariable] : null; if (!commandId) { // Just launch any command if the interactive variable is not contributed by the adapter #12735 diff --git a/src/vs/workbench/services/part/common/partService.ts b/src/vs/workbench/services/part/common/partService.ts index 4a0475f01eb..5036d29cf88 100644 --- a/src/vs/workbench/services/part/common/partService.ts +++ b/src/vs/workbench/services/part/common/partService.ts @@ -27,13 +27,6 @@ export interface ILayoutOptions { toggleMaximizedPanel?: boolean; } -export interface IZenModeOptions { - noFullScreen?: boolean; - keepStatusBar?: boolean; - keepTabs?: boolean; - keepActivityBar?: boolean; -} - export const IPartService = createDecorator('partService'); export interface IPartService { @@ -123,5 +116,5 @@ export interface IPartService { /** * Toggles the workbench in and out of zen mode - parts get hidden and window goes fullscreen. */ - toggleZenMode(options?: IZenModeOptions): void; + toggleZenMode(): void; } \ No newline at end of file diff --git a/src/vs/workbench/services/search/node/textSearch.ts b/src/vs/workbench/services/search/node/textSearch.ts index 65ef0a1121b..eb82da7bf10 100644 --- a/src/vs/workbench/services/search/node/textSearch.ts +++ b/src/vs/workbench/services/search/node/textSearch.ts @@ -121,7 +121,7 @@ export class Engine implements ISearchEngine { }; // Walk over the file system - let nextBatch = []; + let nextBatch: string[] = []; let nextBatchBytes = 0; const batchFlushBytes = 2 ** 20; // 1MB this.walker.walk(this.config.rootFolders, this.config.extraFiles, result => { diff --git a/src/vs/workbench/services/search/node/worker/searchWorker.ts b/src/vs/workbench/services/search/node/worker/searchWorker.ts index 2f5c52809fa..4f845374d15 100644 --- a/src/vs/workbench/services/search/node/worker/searchWorker.ts +++ b/src/vs/workbench/services/search/node/worker/searchWorker.ts @@ -177,7 +177,7 @@ export class SearchWorkerEngine { let lineNumber = 0; let lastBufferHadTraillingCR = false; - const decodeBuffer = (buffer: NodeBuffer, start, end): string => { + const decodeBuffer = (buffer: NodeBuffer, start: number, end: number): string => { if (options.encoding === UTF8 || options.encoding === UTF8_with_bom) { return buffer.toString(undefined, start, end); // much faster to use built in toString() when encoding is default } diff --git a/src/vs/workbench/services/telemetry/common/workspaceStats.ts b/src/vs/workbench/services/telemetry/common/workspaceStats.ts index 79ad84e73e9..7f64418ade1 100644 --- a/src/vs/workbench/services/telemetry/common/workspaceStats.ts +++ b/src/vs/workbench/services/telemetry/common/workspaceStats.ts @@ -60,7 +60,8 @@ function extractDomain(url: string): string { } export function getDomainsOfRemotes(text: string, whitelist: string[]): string[] { - let domains = new ArraySet(), match; + let domains = new ArraySet(); + let match: RegExpExecArray; while (match = RemoteMatcher.exec(text)) { let domain = extractDomain(match[1]); if (domain) { @@ -242,7 +243,7 @@ export class WorkspaceStats { ); } - private reportAzure(uri) { + private reportAzure(uri: URI) { const tags: Tags = Object.create(null); this.reportAzureNode(uri, tags).then((tags) => { return this.reportAzureJava(uri, tags); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 811cd656b2e..1353b624115 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -6,7 +6,7 @@ import nls = require('vs/nls'); import Event, { Emitter } from 'vs/base/common/event'; -import { TPromise } from 'vs/base/common/winjs.base'; +import { TPromise, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base'; import { onUnexpectedError } from 'vs/base/common/errors'; import { guessMimeTypes } from 'vs/base/common/mime'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -56,7 +56,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private autoSaveAfterMilliesEnabled: boolean; private autoSavePromise: TPromise; private contentChangeEventScheduler: RunOnceScheduler; - private mapPendingSaveToVersionId: { [versionId: string]: TPromise }; + private saveSequentializer: SaveSequentializer; private disposed: boolean; private inConflictResolutionMode: boolean; private inErrorMode: boolean; @@ -92,7 +92,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.dirty = false; this.versionId = 0; this.lastSaveAttemptTime = 0; - this.mapPendingSaveToVersionId = {}; + this.saveSequentializer = new SaveSequentializer(); this.contentChangeEventScheduler = new RunOnceScheduler(() => this._onDidContentChange.fire(StateChange.CONTENT_CHANGE), TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY); this.toDispose.push(this.contentChangeEventScheduler); @@ -131,6 +131,17 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateTextEditorModelMode(); } + private updateTextEditorModelMode(modeId?: string): void { + if (!this.textEditorModel) { + return; + } + + const firstLineText = this.getFirstLineText(this.textEditorModel.getValue()); + const mode = this.getOrCreateMode(this.modeService, modeId, firstLineText); + + this.modelService.setMode(this.textEditorModel, mode); + } + public get onDidContentChange(): Event { return this._onDidContentChange.event; } @@ -479,7 +490,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Only trigger save if the version id has not changed meanwhile if (versionId === this.versionId) { - this.doSave(versionId, SaveReason.AUTO); // Very important here to not return the promise because if the timeout promise is canceled it will bubble up the error otherwise - do not change + this.doSave(versionId, SaveReason.AUTO).done(null, onUnexpectedError); // Very important here to not return the promise because if the timeout promise is canceled it will bubble up the error otherwise - do not change } }); @@ -517,11 +528,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Scenario: user invoked the save action multiple times quickly for the same contents // while the save was not yet finished to disk // - const pendingSave = this.mapPendingSaveToVersionId[versionId]; - if (pendingSave) { + if (this.saveSequentializer.hasPendingSave(versionId)) { diag(`doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource, new Date()); - return pendingSave; + return this.saveSequentializer.pendingSave; } // Return early if not dirty or version changed meanwhile @@ -537,20 +547,19 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return TPromise.as(null); } - // Return if currently saving by scheduling another auto save. Never ever must 2 saves execute at the same time because - // this can lead to dirty writes and race conditions + // Return if currently saving by storing this save request as the next save that should happen. + // Never ever must 2 saves execute at the same time because this can lead to dirty writes and race conditions. // - // Scenario: auto save was triggered and is currently busy saving to disk. this takes long enough that another auto save - // kicks in. since we never want to trigger 2 saves at the same time, we push out this auto save for the - // configured auto save delay assuming that it can proceed next time it triggers. + // Scenario A: auto save was triggered and is currently busy saving to disk. this takes long enough that another auto save + // kicks in. + // Scenario B: save is very slow (e.g. network share) and the user manages to change the buffer and trigger another save + // while the first save has not returned yet. // - if (this.isBusySaving()) { + if (this.saveSequentializer.hasPendingSave()) { diag(`doSave(${versionId}) - exit - because busy saving`, this.resource, new Date()); - // Avoid endless loop here and guard if auto save is disabled - if (this.autoSaveAfterMilliesEnabled) { - return this.doAutoSave(versionId); - } + // Register this as the next upcoming save and return + return this.saveSequentializer.setNext(() => this.doSave(this.versionId /* make sure to use latest version id here */, reason, overwriteReadonly, overwriteEncoding)); } // Push all edit operations to the undo stack so that the user has a chance to @@ -579,11 +588,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }).then(onCompleteOrError, onCompleteOrError); } - this.mapPendingSaveToVersionId[versionId] = saveParticipantPromise.then(newVersionId => { + // mark the save participant as current pending save operation + return this.saveSequentializer.setPending(versionId, saveParticipantPromise.then(newVersionId => { - // remove save participant promise from pending saves and update versionId with - // its new value (if pre-save changes happened) - delete this.mapPendingSaveToVersionId[versionId]; + // update versionId with its new value (if pre-save changes happened) versionId = newVersionId; // Clear error flag since we are trying to save again @@ -593,8 +601,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.lastSaveAttemptTime = Date.now(); // Save to Disk + // mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering) diag(`doSave(${versionId}) - before updateContent()`, this.resource, new Date()); - this.mapPendingSaveToVersionId[versionId] = this.fileService.updateContent(this.versionOnDiskStat.resource, this.getValue(), { + return this.saveSequentializer.setPending(newVersionId, this.fileService.updateContent(this.versionOnDiskStat.resource, this.getValue(), { overwriteReadonly, overwriteEncoding, mtime: this.versionOnDiskStat.mtime, @@ -603,9 +612,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }).then((stat: IFileStat) => { diag(`doSave(${versionId}) - after updateContent()`, this.resource, new Date()); - // Remove from pending saves - delete this.mapPendingSaveToVersionId[versionId]; - // Telemetry this.telemetryService.publicLog('filePUT', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.versionOnDiskStat.resource.fsPath) }); @@ -628,9 +634,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }, error => { diag(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource, new Date()); - // Remove from pending saves - delete this.mapPendingSaveToVersionId[versionId]; - // Flag as error state this.inErrorMode = true; @@ -639,12 +642,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit as event this._onDidStateChange.fire(StateChange.SAVE_ERROR); - }); - - return this.mapPendingSaveToVersionId[versionId]; - }); - - return this.mapPendingSaveToVersionId[versionId]; + })); + })); } private setDirty(dirty: boolean): () => void { @@ -742,7 +741,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return ModelState.SAVED; } - if (this.isBusySaving()) { + if (this.saveSequentializer.hasPendingSave()) { return ModelState.PENDING_SAVE; } @@ -751,10 +750,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private isBusySaving(): boolean { - return !types.isEmptyObject(this.mapPendingSaveToVersionId); - } - public getEncoding(): string { return this.preferredEncoding || this.contentEncoding; } @@ -850,6 +845,97 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } +interface IPendingSave { + versionId: number; + promise: TPromise; +} + +interface ISaveOperation { + promise: TPromise; + promiseValue: TValueCallback; + promiseError: ErrorCallback; + run: () => TPromise; +} + +export class SaveSequentializer { + private _pendingSave: IPendingSave; + private _nextSave: ISaveOperation; + + public hasPendingSave(versionId?: number): boolean { + if (!this._pendingSave) { + return false; + } + + if (typeof versionId === 'number') { + return this._pendingSave.versionId === versionId; + } + + return !!this._pendingSave; + } + + public get pendingSave(): TPromise { + return this._pendingSave ? this._pendingSave.promise : void 0; + } + + public setPending(versionId: number, promise: TPromise): TPromise { + this._pendingSave = { versionId, promise }; + + promise.done(() => this.donePending(versionId), () => this.donePending(versionId)); + + return promise; + } + + private donePending(versionId: number): void { + if (this._pendingSave && versionId === this._pendingSave.versionId) { + + // only set pending to done if the promise finished that is associated with that versionId + this._pendingSave = void 0; + + // schedule the next save now that we are free if we have any + this.triggerNextSave(); + } + } + + private triggerNextSave(): void { + if (this._nextSave) { + const saveOperation = this._nextSave; + this._nextSave = void 0; + + // Run next save and complete on the associated promise + saveOperation.run().done(saveOperation.promiseValue, saveOperation.promiseError); + } + } + + public setNext(run: () => TPromise): TPromise { + + // this is our first next save, so we create associated promise with it + // so that we can return a promise that completes when the save operation + // has completed. + if (!this._nextSave) { + let promiseValue: TValueCallback; + let promiseError: ErrorCallback; + const promise = new TPromise((c, e) => { + promiseValue = c; + promiseError = e; + }); + + this._nextSave = { + run, + promise, + promiseValue, + promiseError + }; + } + + // we have a previous next save, just overwrite it + else { + this._nextSave.run = run; + } + + return this._nextSave.promise; + } +} + class DefaultSaveErrorHandler implements ISaveErrorHandler { constructor( @IMessageService private messageService: IMessageService) { } diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index c65c4a8fcc1..d622ec34313 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -23,7 +23,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -61,7 +60,6 @@ export abstract class TextFileService implements ITextFileService { @IWorkspaceContextService private contextService: IWorkspaceContextService, @IConfigurationService private configurationService: IConfigurationService, @ITelemetryService private telemetryService: ITelemetryService, - @IEditorGroupService private editorGroupService: IEditorGroupService, @IFileService protected fileService: IFileService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IInstantiationService private instantiationService: IInstantiationService, @@ -564,7 +562,7 @@ export abstract class TextFileService implements ITextFileService { private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI): TPromise { // create the target file empty if it does not exist already - return this.fileService.resolveFile(target).then(stat => stat, () => null).then(stat => stat || this.fileService.createFile(target)).then(stat => { + return this.fileService.resolveFile(target).then(stat => stat, () => null).then(stat => stat || this.fileService.updateContent(target, '')).then(stat => { // resolve a model for the file (which can be binary if the file is not a text file) return this.models.loadOrCreate(target).then((targetModel: ITextFileEditorModel) => { diff --git a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts index 9455e4c12d7..31689440954 100644 --- a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts @@ -22,7 +22,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelBuilder } from 'vs/editor/node/model/modelBuilder'; import product from 'vs/platform/node/product'; @@ -46,7 +45,6 @@ export class TextFileService extends AbstractTextFileService { @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IModeService private modeService: IModeService, - @IEditorGroupService editorGroupService: IEditorGroupService, @IWindowIPCService private windowService: IWindowIPCService, @IModelService private modelService: IModelService, @IEnvironmentService environmentService: IEnvironmentService, @@ -55,7 +53,7 @@ export class TextFileService extends AbstractTextFileService { @IStorageService private storageService: IStorageService, @IWindowsService windowsService: IWindowsService ) { - super(lifecycleService, contextService, configurationService, telemetryService, editorGroupService, fileService, untitledEditorService, instantiationService, messageService, environmentService, backupFileService, windowsService); + super(lifecycleService, contextService, configurationService, telemetryService, fileService, untitledEditorService, instantiationService, messageService, environmentService, backupFileService, windowsService); } public resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise { diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 1edb5fa0d55..0bf4c5633d3 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -9,7 +9,7 @@ import * as assert from 'assert'; import { TPromise } from 'vs/base/common/winjs.base'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode } from 'vs/workbench/common/editor'; -import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; +import { TextFileEditorModel, SaveSequentializer } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService, ModelState, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; import { workbenchInstantiationService, TestTextFileService, createFileInput } from 'vs/workbench/test/workbenchTestServices'; import { onError, toResource } from 'vs/base/test/common/utils'; @@ -376,4 +376,95 @@ suite('Files - TextFileEditorModel', () => { }); }, error => onError(error, done)); }); + + test('SaveSequentializer - pending basics', function (done) { + const sequentializer = new SaveSequentializer(); + + assert.ok(!sequentializer.hasPendingSave()); + assert.ok(!sequentializer.hasPendingSave(2323)); + assert.ok(!sequentializer.pendingSave); + + // pending removes itself after done + sequentializer.setPending(1, TPromise.as(null)); + assert.ok(!sequentializer.hasPendingSave()); + assert.ok(!sequentializer.hasPendingSave(1)); + assert.ok(!sequentializer.pendingSave); + + // pending removes itself after done (use timeout) + sequentializer.setPending(2, TPromise.timeout(1)); + assert.ok(sequentializer.hasPendingSave()); + assert.ok(sequentializer.hasPendingSave(2)); + assert.ok(!sequentializer.hasPendingSave(1)); + assert.ok(sequentializer.pendingSave); + + return TPromise.timeout(2).then(() => { + assert.ok(!sequentializer.hasPendingSave()); + assert.ok(!sequentializer.hasPendingSave(2)); + assert.ok(!sequentializer.pendingSave); + + done(); + }); + }); + + test('SaveSequentializer - pending and next (finishes instantly)', function (done) { + const sequentializer = new SaveSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, TPromise.timeout(1).then(() => { pendingDone = true; return null; })); + + // next finishes instantly + let nextDone = false; + const res = sequentializer.setNext(() => TPromise.as(null).then(() => { nextDone = true; return null; })); + + return res.done(() => { + assert.ok(pendingDone); + assert.ok(nextDone); + + done(); + }); + }); + + test('SaveSequentializer - pending and next (finishes after timeout)', function (done) { + const sequentializer = new SaveSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, TPromise.timeout(1).then(() => { pendingDone = true; return null; })); + + // next finishes after timeout + let nextDone = false; + const res = sequentializer.setNext(() => TPromise.timeout(1).then(() => { nextDone = true; return null; })); + + return res.done(() => { + assert.ok(pendingDone); + assert.ok(nextDone); + + done(); + }); + }); + + test('SaveSequentializer - pending and multiple next (last one wins)', function (done) { + const sequentializer = new SaveSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, TPromise.timeout(1).then(() => { pendingDone = true; return null; })); + + // next finishes after timeout + let firstDone = false; + let firstRes = sequentializer.setNext(() => TPromise.timeout(2).then(() => { firstDone = true; return null; })); + + let secondDone = false; + let secondRes = sequentializer.setNext(() => TPromise.timeout(3).then(() => { secondDone = true; return null; })); + + let thirdDone = false; + let thirdRes = sequentializer.setNext(() => TPromise.timeout(4).then(() => { thirdDone = true; return null; })); + + return TPromise.join([firstRes, secondRes, thirdRes]).then(() => { + assert.ok(pendingDone); + assert.ok(!firstDone); + assert.ok(!secondDone); + assert.ok(thirdDone); + + done(); + }); + }); }); \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index b10e5b3ecd1..4d3887107d8 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -163,7 +163,7 @@ suite('Files - TextFileService', () => { return untitled.resolve().then((model: UntitledEditorModel) => { assert.ok(!service.isDirty(untitled.getResource())); assert.equal(service.getDirty().length, 1); - model.setValue('changed'); + model.textEditorModel.setValue('changed'); assert.ok(service.isDirty(untitled.getResource())); assert.equal(service.getDirty().length, 2); diff --git a/src/vs/workbench/services/themes/electron-browser/stylesContributions.ts b/src/vs/workbench/services/themes/electron-browser/stylesContributions.ts index 27832128a7a..e6ed64d35cc 100644 --- a/src/vs/workbench/services/themes/electron-browser/stylesContributions.ts +++ b/src/vs/workbench/services/themes/electron-browser/stylesContributions.ts @@ -94,7 +94,7 @@ export class TokenStylesContribution { public contributeStyles(themeId: string, themeDocument: IThemeDocument, cssRules: string[]): void { let theme = new Theme(themeId, themeDocument); - theme.getSettings().forEach((s: IThemeSetting, index, arr) => { + theme.getSettings().forEach((s: IThemeSetting, index: number, arr: IThemeSetting[]) => { // @martin TS(2.0.2) - s.scope is already a string[] so no need for all this checking. // However will add a cast at split to keep semantic in case s.scope is wrongly typed. let scope: string | string[] = s.scope; @@ -127,7 +127,7 @@ export class TokenStylesContribution { //statements.push(`background-color: ${background};`); break; case 'fontStyle': - let segments = value.split(' '); + let segments: string[] = value.split(' '); segments.forEach(s => { switch (s) { case 'italic': diff --git a/src/vs/workbench/services/themes/electron-browser/themeService.ts b/src/vs/workbench/services/themes/electron-browser/themeService.ts index 976c3dd0144..58bdcfc0781 100644 --- a/src/vs/workbench/services/themes/electron-browser/themeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/themeService.ts @@ -434,7 +434,7 @@ export class ThemeService implements IThemeService { private _updateIconTheme(onApply: (theme: IInternalThemeData) => void): TPromise { return this.getFileIconThemes().then(allIconSets => { - let iconSetData; + let iconSetData: IInternalThemeData; for (let iconSet of allIconSets) { if (iconSet.id === this.currentIconTheme) { iconSetData = iconSet; @@ -545,7 +545,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, ic let fileExtensions = associations.fileExtensions; if (fileExtensions) { for (let fileExtension in fileExtensions) { - let selectors = []; + let selectors: string[] = []; let segments = fileExtension.toLowerCase().split('.'); for (let i = 0; i < segments.length; i++) { selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); @@ -556,7 +556,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, ic let fileNames = associations.fileNames; if (fileNames) { for (let fileName in fileNames) { - let selectors = []; + let selectors: string[] = []; fileName = fileName.toLowerCase(); selectors.push(`.${escapeCSS(fileName)}-name-file-icon`); let segments = fileName.split('.'); diff --git a/src/vs/workbench/services/timer/common/timerService.ts b/src/vs/workbench/services/timer/common/timerService.ts index 8366aa6fe64..19d7c77d7ba 100644 --- a/src/vs/workbench/services/timer/common/timerService.ts +++ b/src/vs/workbench/services/timer/common/timerService.ts @@ -19,6 +19,7 @@ export interface IStartupMetrics { version: number; ellapsed: number; timers: { + ellapsedAppReady?: number; ellapsedWindowLoad?: number; ellapsedWindowLoadToRequire: number; ellapsedExtensions: number; @@ -44,6 +45,8 @@ export interface IStartupMetrics { export interface IInitData { start: Date; + appReady: Date; + windowLoad: Date; beforeLoadWorkbenchMain: Date; @@ -69,4 +72,4 @@ export interface ITimerService extends IInitData { restoreEditorsDuration: number; readonly startupMetrics: IStartupMetrics; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/timer/node/timerService.ts b/src/vs/workbench/services/timer/node/timerService.ts index a0eb9caa6f1..ffec104d14e 100644 --- a/src/vs/workbench/services/timer/node/timerService.ts +++ b/src/vs/workbench/services/timer/node/timerService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as timer from 'vs/base/common/timer'; import { ITimerService, IStartupMetrics, IInitData, IMemoryInfo } from 'vs/workbench/services/timer/common/timerService'; import * as os from 'os'; @@ -13,21 +12,15 @@ export class TimerService implements ITimerService { public _serviceBrand: any; - public get start(): Date { return this._start; } - private _start: Date; + public readonly start: Date; + public readonly appReady: Date; + public readonly windowLoad: Date; - public get windowLoad(): Date { return this._windowLoad; }; - private _windowLoad: Date; + public readonly beforeLoadWorkbenchMain: Date; + public readonly afterLoadWorkbenchMain: Date; - public get beforeLoadWorkbenchMain(): Date { return this._beforeLoadWorkbenchMain; }; - private _beforeLoadWorkbenchMain: Date; - public get afterLoadWorkbenchMain(): Date { return this._afterLoadWorkbenchMain; }; - private _afterLoadWorkbenchMain: Date; - - public get isInitialStartup(): boolean { return this._isInitialStartup; }; - private _isInitialStartup: boolean; - public get hasAccessibilitySupport(): boolean { return this._hasAccessibilitySupport; }; - private _hasAccessibilitySupport: boolean; + public readonly isInitialStartup: boolean; + public readonly hasAccessibilitySupport: boolean; public beforeDOMContentLoaded: Date; public afterDOMContentLoaded: Date; @@ -51,18 +44,15 @@ export class TimerService implements ITimerService { private _startupMetrics: IStartupMetrics; constructor(initData: IInitData, private isEmptyWorkbench: boolean) { - this._start = initData.start; + this.start = initData.start; + this.appReady = initData.appReady; + this.windowLoad = initData.windowLoad; - this._windowLoad = initData.windowLoad; + this.beforeLoadWorkbenchMain = initData.beforeLoadWorkbenchMain; + this.afterLoadWorkbenchMain = initData.afterLoadWorkbenchMain; - this._beforeLoadWorkbenchMain = initData.beforeLoadWorkbenchMain; - this._afterLoadWorkbenchMain = initData.afterLoadWorkbenchMain; - - this._isInitialStartup = initData.isInitialStartup; - this._hasAccessibilitySupport = initData.hasAccessibilitySupport; - - // forward start time to time keeper - timer.TimeKeeper.PARSE_TIME = initData.isInitialStartup ? initData.start : initData.windowLoad; + this.isInitialStartup = initData.isInitialStartup; + this.hasAccessibilitySupport = initData.hasAccessibilitySupport; } public computeStartupMetrics(): void { @@ -120,7 +110,8 @@ export class TimerService implements ITimerService { }; if (initialStartup) { - this._startupMetrics.timers.ellapsedWindowLoad = Math.round(this.windowLoad.getTime() - this.start.getTime()); + this._startupMetrics.timers.ellapsedAppReady = Math.round(this.appReady.getTime() - this.start.getTime()); + this._startupMetrics.timers.ellapsedWindowLoad = Math.round(this.windowLoad.getTime() - this.appReady.getTime()); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 89e92367803..06d23ce51f7 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -34,6 +34,10 @@ class MyPart extends Part { assert.strictEqual(parent, this.expectedParent); return super.createStatusArea(parent); } + + public getMemento(storageService: IStorageService): any { + return super.getMemento(storageService); + } } class MyPart2 extends Part { diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index d084974040f..8607a049eb2 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -130,11 +130,11 @@ suite('Workbench BaseEditor', () => { let options = new EditorOptions(); assert(!e.isVisible()); - assert(!e.getInput()); - assert(!e.getOptions()); + assert(!e.input); + assert(!e.options); e.setInput(input, options).then(() => { - assert.strictEqual(input, e.getInput()); - assert.strictEqual(options, e.getOptions()); + assert.strictEqual(input, e.input); + assert.strictEqual(options, e.options); e.setVisible(true); assert(e.isVisible()); @@ -145,8 +145,8 @@ suite('Workbench BaseEditor', () => { e.clearInput(); e.setVisible(false); assert(!e.isVisible()); - assert(!e.getInput()); - assert(!e.getOptions()); + assert(!e.input); + assert(!e.options); assert(!e.getControl()); }).done(() => done()); }); diff --git a/src/vs/workbench/test/common/editor/stringEditorInput.test.ts b/src/vs/workbench/test/common/editor/stringEditorInput.test.ts index b6b2926d710..9d4fda893a1 100644 --- a/src/vs/workbench/test/common/editor/stringEditorInput.test.ts +++ b/src/vs/workbench/test/common/editor/stringEditorInput.test.ts @@ -20,7 +20,6 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import WorkbenchEditorService = require('vs/workbench/services/editor/common/editorService'); suite('Workbench - StringEditorInput', () => { - let instantiationService: TestInstantiationService; let editorService: WorkbenchEditorService.IWorkbenchEditorService; let modelService: IModelService; @@ -34,13 +33,12 @@ suite('Workbench - StringEditorInput', () => { }); test('StringEditorInput', function (done) { + let input: StringEditorInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); + const otherInput: StringEditorInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'othervalue', 'mode', false); + const otherInputSame: StringEditorInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); - let input = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); - let otherInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'othervalue', 'mode', false); - let otherInputSame = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); - - let inputSingleton = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', true); - let otherInputSingleton = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'othervalue', 'mode', true); + const inputSingleton: StringEditorInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', true); + const otherInputSingleton: StringEditorInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'othervalue', 'mode', true); assert(inputSingleton.matches(otherInputSingleton)); (otherInputSingleton).singleton = false; assert(!inputSingleton.matches(otherInputSingleton)); @@ -55,11 +53,11 @@ suite('Workbench - StringEditorInput', () => { input = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); input.resolve(true).then(resolved => { - let resolvedModelA = resolved; + const resolvedModelA = resolved; return input.resolve(true).then(resolved => { assert(resolvedModelA === resolved); // assert: Resolved Model cached per instance - let otherInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); + const otherInput: StringEditorInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); return otherInput.resolve(true).then(resolved => { assert(resolvedModelA !== resolved); // NOT assert: Different instance, different model @@ -68,7 +66,7 @@ suite('Workbench - StringEditorInput', () => { return input.resolve(true).then(resolved => { assert(resolvedModelA !== resolved); // Different instance, because input got disposed - let model = (resolved).textEditorModel; + const model = (resolved).textEditorModel; return input.resolve(true).then(againResolved => { assert(model === (againResolved).textEditorModel); // Models should not differ because string input is constant @@ -81,27 +79,23 @@ suite('Workbench - StringEditorInput', () => { }); test('StringEditorInput - setValue, clearValue, append', function () { - let input = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); + const input: StringEditorInput = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); assert.strictEqual(input.getValue(), 'value'); input.setValue('foo'); assert.strictEqual(input.getValue(), 'foo'); - input.clearValue(); + input.setValue(''); assert(!input.getValue()); - input.append('1'); - assert.strictEqual(input.getValue(), '1'); - input.append('2'); - assert.strictEqual(input.getValue(), '12'); }); test('Input.matches() - StringEditorInput', function () { - let inst = new TestInstantiationService(); + const inst = new TestInstantiationService(); - let stringEditorInput = inst.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); - let promiseEditorInput = inst.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'inMemory', authority: null, path: 'thePath' })); + const stringEditorInput: StringEditorInput = inst.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); + const promiseEditorInput: StringEditorInput = inst.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'inMemory', authority: null, path: 'thePath' })); - let stringEditorInput2 = inst.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); - let promiseEditorInput2 = inst.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'inMemory', authority: null, path: 'thePath' })); + const stringEditorInput2: StringEditorInput = inst.createInstance(StringEditorInput, 'name', 'description', 'value', 'mode', false); + const promiseEditorInput2: StringEditorInput = inst.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'inMemory', authority: null, path: 'thePath' })); assert.strictEqual(stringEditorInput.matches(null), false); assert.strictEqual(promiseEditorInput.matches(null), false); @@ -115,6 +109,7 @@ suite('Workbench - StringEditorInput', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + return instantiationService.createInstance(ModelServiceImpl); } }); \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/stringEditorModel.test.ts b/src/vs/workbench/test/common/editor/stringEditorModel.test.ts index fb28147b5b8..8258b2c28b2 100644 --- a/src/vs/workbench/test/common/editor/stringEditorModel.test.ts +++ b/src/vs/workbench/test/common/editor/stringEditorModel.test.ts @@ -16,7 +16,6 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; suite('Workbench - StringEditorModel', () => { - let instantiationService: TestInstantiationService; setup(() => { @@ -26,11 +25,11 @@ suite('Workbench - StringEditorModel', () => { test('StringEditorModel', function (done) { instantiationService.stub(IModelService, stubModelService(instantiationService)); - let m = instantiationService.createInstance(StringEditorModel, 'value', 'mode', null); + const m: StringEditorModel = instantiationService.createInstance(StringEditorModel, 'value', 'mode', null); m.load().then(model => { assert(model === m); - let textEditorModel = m.textEditorModel; + const textEditorModel = m.textEditorModel; assert.strictEqual(textEditorModel.getValue(), 'value'); assert.strictEqual(m.isResolved(), true); @@ -46,36 +45,22 @@ suite('Workbench - StringEditorModel', () => { }); }); - test('StringEditorModel - setValue, clearValue, append, trim', function (done) { + test('StringEditorModel - setValue', function (done) { instantiationService.stub(IModelService, stubModelService(instantiationService)); - let m = instantiationService.createInstance(StringEditorModel, 'value', 'mode', null); + const m: StringEditorModel = instantiationService.createInstance(StringEditorModel, 'value', 'mode', null); m.load().then(model => { assert(model === m); - let textEditorModel = m.textEditorModel; + const textEditorModel = m.textEditorModel; assert.strictEqual(textEditorModel.getValue(), 'value'); m.setValue('foobar'); assert.strictEqual(m.getValue(), 'foobar'); assert.strictEqual(textEditorModel.getValue(), 'foobar'); - m.clearValue(); + m.setValue(''); assert(!m.getValue()); assert(!textEditorModel.getValue()); - - m.append('1'); - assert.strictEqual(m.getValue(), '1'); - assert.strictEqual(textEditorModel.getValue(), '1'); - - m.append('1'); - assert.strictEqual(m.getValue(), '11'); - assert.strictEqual(textEditorModel.getValue(), '11'); - - m.setValue('line\nline\nline'); - m.trim(2); - - assert.strictEqual(m.getValue(), 'line\nline'); - assert.strictEqual(textEditorModel.getValue(), 'line\nline'); }).done(() => { m.dispose(); done(); @@ -84,6 +69,7 @@ suite('Workbench - StringEditorModel', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + return instantiationService.createInstance(ModelServiceImpl); } }); \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledEditor.test.ts index a4ca3e9ce16..251def9a044 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledEditor.test.ts @@ -162,21 +162,21 @@ suite('Workbench - Untitled Editor', () => { }); input.resolve().then((model: UntitledEditorModel) => { - model.append('foo'); + model.textEditorModel.setValue('foo'); assert.equal(counter, 0, 'Dirty model should not trigger event immediately'); TPromise.timeout(3).then(() => { assert.equal(counter, 1, 'Dirty model should trigger event'); - model.append('bar'); + model.textEditorModel.setValue('bar'); TPromise.timeout(3).then(() => { assert.equal(counter, 2, 'Content change when dirty should trigger event'); - model.clearValue(); + model.textEditorModel.setValue(''); TPromise.timeout(3).then(() => { assert.equal(counter, 3, 'Manual revert should trigger event'); - model.append('foo'); + model.textEditorModel.setValue('foo'); TPromise.timeout(3).then(() => { assert.equal(counter, 4, 'Dirty model should trigger event'); diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts index 52845c580bb..1d325306e0a 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts @@ -22,7 +22,6 @@ import { SearchService } from 'vs/workbench/services/search/node/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TestEnvironmentService, TestEditorService, TestEditorGroupService } from 'vs/workbench/test/workbenchTestServices'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import * as Timer from 'vs/base/common/timer'; import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -31,6 +30,23 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; + +namespace Timer { + export interface ITimerEvent { + id: number; + topic: string; + name: string; + description: string; + data: any; + + startTime: Date; + stopTime: Date; + + stop(stopTime?: Date): void; + timeTaken(): number; + } +} + declare var __dirname: string; // Checkout sources to run against: diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index b21fbb11261..4321cd6487b 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -110,7 +110,6 @@ export class TestTextFileService extends TextFileService { @IConfigurationService configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IEditorGroupService editorGroupService: IEditorGroupService, @IFileService fileService: IFileService, @IUntitledEditorService untitledEditorService: IUntitledEditorService, @IInstantiationService instantiationService: IInstantiationService, @@ -118,7 +117,7 @@ export class TestTextFileService extends TextFileService { @IBackupFileService backupFileService: IBackupFileService, @IWindowsService windowsService: IWindowsService ) { - super(lifecycleService, contextService, configurationService, telemetryService, editorGroupService, fileService, untitledEditorService, instantiationService, messageService, TestEnvironmentService, backupFileService, windowsService); + super(lifecycleService, contextService, configurationService, telemetryService, fileService, untitledEditorService, instantiationService, messageService, TestEnvironmentService, backupFileService, windowsService); } public setPromptPath(path: string): void { @@ -888,8 +887,7 @@ export class TestWindowsService implements IWindowsService { } // Global methods - // TODO@joao: rename, shouldn't this be openWindow? - windowOpen(paths: string[], forceNewWindow?: boolean): TPromise { + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise { return TPromise.as(void 0); } openNewWindow(): TPromise {