diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 3130c7dd97e..846b10ecdce 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs'] const builtInExtensions = [ { name: 'ms-vscode.node-debug', version: '1.9.0' }, - { name: 'ms-vscode.node-debug2', version: '1.9.0' } + { 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/extensions/cpp/syntaxes/c.json b/extensions/cpp/syntaxes/c.json index 8c481e575ad..28ecbab7b57 100644 --- a/extensions/cpp/syntaxes/c.json +++ b/extensions/cpp/syntaxes/c.json @@ -318,9 +318,6 @@ { "include": "#preprocessor-rule-other-block" }, - { - "include": "#sizeof" - }, { "include": "#access" }, @@ -458,7 +455,7 @@ "line_continuation_character": { "patterns": [ { - "match": "(\\\\)\\s*\\n", + "match": "(\\\\)\\n", "captures": { "1": { "name": "constant.character.escape.line-continuation.c" @@ -891,6 +888,15 @@ } }, "patterns": [ + { + "include": "#access" + }, + { + "include": "#libc" + }, + { + "include": "#c_function_call" + }, { "include": "$self" } @@ -976,5 +982,5 @@ ] } }, - "version": "https://github.com/atom/language-c/commit/2a5fafe1d86f690b5ab2c877cea2fc6a598e001a" + "version": "https://github.com/atom/language-c/commit/0d0f32388e73fc91a86f4c31ff59c36191869d63" } \ No newline at end of file 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/javascript/src/features/packageJSONContribution.ts b/extensions/javascript/src/features/packageJSONContribution.ts index 25f26865f57..573b8b1608f 100644 --- a/extensions/javascript/src/features/packageJSONContribution.ts +++ b/extensions/javascript/src/features/packageJSONContribution.ts @@ -67,7 +67,7 @@ export class PackageJSONContribution implements IJSONContribution { let name = keys[0]; let insertText = new SnippetString().appendText(JSON.stringify(name)); if (addValue) { - insertText.appendText(': ').appendPlaceholder('*'); + insertText.appendText(': "').appendPlaceholder('').appendText('"'); if (!isLast) { insertText.appendText(','); } @@ -99,7 +99,7 @@ export class PackageJSONContribution implements IJSONContribution { this.mostDependedOn.forEach((name) => { let insertText = new SnippetString().appendText(JSON.stringify(name)); if (addValue) { - insertText.appendText(': ').appendPlaceholder('*'); + insertText.appendText(': "').appendPlaceholder('').appendText('"'); if (!isLast) { insertText.appendText(','); } diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage b/extensions/markdown/syntaxes/markdown.tmLanguage index bc8de1994d7..30afb189404 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 @@ -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 ba06d507553..54bb5e84135 100644 --- a/extensions/markdown/test/colorize-results/test_md.json +++ b/extensions/markdown/test/colorize-results/test_md.json @@ -1584,8 +1584,19 @@ } }, { - "c": "````application/json", - "t": "text.html.markdown meta.paragraph.markdown", + "c": "````", + "t": "text.html.markdown markup.fenced_code.block.markdown punctuation.definition.markdown", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "application/json", + "t": "text.html.markdown markup.fenced_code.block.markdown fenced_code.block.language", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1596,7 +1607,7 @@ }, { "c": " { value: [\"or with a mime type\"] }", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown markup.fenced_code.block.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1607,7 +1618,7 @@ }, { "c": "````", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown markup.fenced_code.block.markdown punctuation.definition.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1838,7 +1849,7 @@ }, { "c": "~~~", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown markup.fenced_code.block.markdown punctuation.definition.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1849,7 +1860,7 @@ }, { "c": "// Markdown extra adds un-indented code blocks too", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown markup.fenced_code.block.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1859,74 +1870,8 @@ } }, { - "c": "if (this", - "t": "text.html.markdown meta.paragraph.markdown", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "_", - "t": "text.html.markdown meta.paragraph.markdown markup.italic.markdown punctuation.definition.italic.markdown", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "is", - "t": "text.html.markdown meta.paragraph.markdown markup.italic.markdown", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "_", - "t": "text.html.markdown meta.paragraph.markdown markup.italic.markdown punctuation.definition.italic.markdown", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "more_code == true ", - "t": "text.html.markdown meta.paragraph.markdown", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "&&", - "t": "text.html.markdown meta.paragraph.markdown meta.other.valid-ampersand.markdown", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " !indented) {", - "t": "text.html.markdown meta.paragraph.markdown", + "c": "if (this_is_more_code == true && !indented) {", + "t": "text.html.markdown markup.fenced_code.block.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1937,7 +1882,7 @@ }, { "c": " // tild wrapped code blocks, also not indented", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown markup.fenced_code.block.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1948,7 +1893,7 @@ }, { "c": "}", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown markup.fenced_code.block.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1959,7 +1904,7 @@ }, { "c": "~~~", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown markup.fenced_code.block.markdown punctuation.definition.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 65a725816fa..1894dab23ba 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -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/package.json b/package.json index 869390297bc..8642c2d76e8 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" }, @@ -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/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/common/arrays.ts b/src/vs/base/common/arrays.ts index 7c6db6d75d8..176cc70a501 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -13,25 +13,12 @@ export function tail(array: T[], n: number = 0): T { return array[array.length - (1 + n)]; } -/** - * Iterates the provided array and allows to remove - * elements while iterating. - */ -export function forEach(array: T[], callback: (element: T, remove: Function) => void): void { - for (var i = 0, len = array.length; i < len; i++) { - callback(array[i], function () { - array.splice(i, 1); - i--; len--; - }); - } -} - export function equals(one: T[], other: T[], itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean { if (one.length !== other.length) { 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; } @@ -105,29 +92,6 @@ export function top(array: T[], compare: (a: T, b: T) => number, n: number): return result; } -export function merge(arrays: T[][], hashFn?: (element: T) => string): T[] { - const result = new Array(); - if (!hashFn) { - for (let i = 0, len = arrays.length; i < len; i++) { - result.push.apply(result, arrays[i]); - } - } else { - const map: { [k: string]: boolean } = {}; - for (let i = 0; i < arrays.length; i++) { - for (let j = 0; j < arrays[i].length; j++) { - let element = arrays[i][j], - hash = hashFn(element); - - if (!map.hasOwnProperty(hash)) { - map[hash] = true; - result.push(element); - } - } - } - } - return result; -} - /** * @returns a new array with all undefined or null values removed. The original array is not modified at all. */ @@ -139,24 +103,6 @@ export function coalesce(array: T[]): T[] { return array.filter(e => !!e); } -/** - * @returns true if the given item is contained in the array. - */ -export function contains(array: T[], item: T): boolean { - return array.indexOf(item) >= 0; -} - -/** - * Swaps the elements in the array for the provided positions. - */ -export function swap(array: any[], pos1: number, pos2: number): void { - const element1 = array[pos1]; - const element2 = array[pos2]; - - array[pos1] = element2; - array[pos2] = element1; -} - /** * Moves the element in the array for the provided positions. */ @@ -231,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++; } @@ -283,4 +229,4 @@ export function insert(array: T[], element: T): () => void { array.splice(index, 1); } }; -} \ No newline at end of file +} 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/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..d7453bc6b7b 100644 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ b/src/vs/base/parts/tree/browser/treeImpl.ts @@ -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/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index 554207d24aa..026fad8e55b 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 }); + this.windowsService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv }); } else if (args._.length === 0 && args['new-window']) { - usedWindows = this.windowsService.open({ cli: args, userEnv, forceNewWindow: true, forceEmpty: true }); + 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'], 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..b9e11a0702a 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'], 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..56536188d21 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -205,21 +205,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 +223,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,7 +482,7 @@ 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 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/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index 4f3bc62ce2d..bb877af77af 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -5,7 +5,7 @@ 'use strict'; import * as nls from 'vs/nls'; -import { merge } from 'vs/base/common/arrays'; +import { flatten } from 'vs/base/common/arrays'; import { IStringDictionary, forEach, values } from 'vs/base/common/collections'; import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; @@ -34,7 +34,7 @@ class ChangeRecorder { private _fileService: IFileService; - constructor(fileService: IFileService) { + constructor(fileService?: IFileService) { this._fileService = fileService; } @@ -42,24 +42,27 @@ 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: () => merge(values(changes)) + 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/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..17d0ba5a25a --- /dev/null +++ b/src/vs/editor/contrib/quickFix/test/common/quickFixModel.test.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * 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, LanguageIdentifier } from 'vs/editor/common/modes'; + + +suite('QuickFix', () => { + const languageIdentifier = new LanguageIdentifier('foo-lang', 3); + + let uri = URI.parse('fake:path'); + let model = Model.createFromString('foobar foo bar\nfarboo far boo', undefined, languageIdentifier, uri); + let markerService: MarkerService; + let editor: ICommonCodeEditor; + + let reg = CodeActionProviderRegistry.register(languageIdentifier.sid, { + 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(languageIdentifier.sid, { + 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/referenceSearch/browser/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts index 1b9e43d20aa..6dccbb348b3 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts @@ -50,8 +50,8 @@ class DecorationsManager implements IDisposable { private _callOnDispose: IDisposable[] = []; private _callOnModelChange: IDisposable[] = []; - constructor(private editor: ICodeEditor, private model: ReferencesModel) { - this._callOnDispose.push(this.editor.onDidChangeModel(() => this._onModelChanged())); + constructor(private _editor: ICodeEditor, private _model: ReferencesModel) { + this._callOnDispose.push(this._editor.onDidChangeModel(() => this._onModelChanged())); this._onModelChanged(); } @@ -62,30 +62,25 @@ class DecorationsManager implements IDisposable { } private _onModelChanged(): void { - - this.removeDecorations(); this._callOnModelChange = dispose(this._callOnModelChange); - - var model = this.editor.getModel(); - if (!model) { - return; - } - - for (var i = 0, len = this.model.groups.length; i < len; i++) { - if (this.model.groups[i].uri.toString() === model.uri.toString()) { - this._addDecorations(this.model.groups[i]); - return; + const model = this._editor.getModel(); + if (model) { + for (const ref of this._model.groups) { + if (ref.uri.toString() === model.uri.toString()) { + this._addDecorations(ref); + return; + } } } } private _addDecorations(reference: FileReferences): void { - this._callOnModelChange.push(this.editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged(event))); + this._callOnModelChange.push(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged(event))); - this.editor.changeDecorations(accessor => { + this._editor.changeDecorations(accessor => { - var newDecorations: editorCommon.IModelDeltaDecoration[] = []; - var newDecorationsActualIndex: number[] = []; + const newDecorations: editorCommon.IModelDeltaDecoration[] = []; + const newDecorationsActualIndex: number[] = []; for (let i = 0, len = reference.children.length; i < len; i++) { let oneReference = reference.children[i]; @@ -99,26 +94,25 @@ class DecorationsManager implements IDisposable { newDecorationsActualIndex.push(i); } - var decorations = accessor.deltaDecorations([], newDecorations); - - for (var i = 0; i < decorations.length; i++) { + const decorations = accessor.deltaDecorations([], newDecorations); + for (let i = 0; i < decorations.length; i++) { this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); } }); } private _onDecorationChanged(event: editorCommon.IModelDecorationsChangedEvent): void { - var changedDecorations = event.changedDecorations, + const changedDecorations = event.changedDecorations, toRemove: string[] = []; - for (var i = 0, len = changedDecorations.length; i < len; i++) { + for (let i = 0, len = changedDecorations.length; i < len; i++) { let reference = this._decorations.get(changedDecorations[i]); if (!reference) { continue; } - var newRange = this.editor.getModel().getDecorationRange(changedDecorations[i]), - ignore = false; + const newRange = this._editor.getModel().getDecorationRange(changedDecorations[i]); + let ignore = false; if (Range.equalsRange(newRange, reference.range)) { continue; @@ -127,8 +121,8 @@ class DecorationsManager implements IDisposable { ignore = true; } else { - var lineLength = reference.range.endColumn - reference.range.startColumn, - newLineLength = newRange.endColumn - newRange.startColumn; + const lineLength = reference.range.endColumn - reference.range.startColumn; + const newLineLength = newRange.endColumn - newRange.startColumn; if (lineLength !== newLineLength) { ignore = true; @@ -143,7 +137,7 @@ class DecorationsManager implements IDisposable { } } - this.editor.changeDecorations((accessor) => { + this._editor.changeDecorations((accessor) => { for (let i = 0, len = toRemove.length; i < len; i++) { delete this._decorations[toRemove[i]]; } @@ -152,7 +146,7 @@ class DecorationsManager implements IDisposable { } public removeDecorations(): void { - this.editor.changeDecorations(accessor => { + this._editor.changeDecorations(accessor => { this._decorations.forEach((value, key) => { accessor.removeDecoration(key); }); 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/snippetCompletion.ts b/src/vs/editor/contrib/suggest/common/snippetCompletion.ts index 135cb79c70b..453ddf45e10 100644 --- a/src/vs/editor/contrib/suggest/common/snippetCompletion.ts +++ b/src/vs/editor/contrib/suggest/common/snippetCompletion.ts @@ -19,23 +19,32 @@ interface ISnippetPick extends IPickOpenEntry { snippet: ISnippet; } -class NameAndLangId { +class Args { - static fromArg(arg: any): NameAndLangId { + static fromUser(arg: any): Args { if (typeof arg !== 'object') { - return new NameAndLangId(undefined, undefined); + return Args._empty; + } + let {snippet, name, langId} = arg; + if (typeof snippet !== 'string') { + snippet = undefined; } - let {name, langId} = arg; if (typeof name !== 'string') { name = undefined; } if (typeof langId !== 'string') { langId = undefined; } - return new NameAndLangId(name, langId); + return new Args(snippet, name, langId); } - private constructor(public readonly name: string, public readonly langId: string) { + private static _empty = new Args(undefined, undefined, undefined); + + private constructor( + public readonly snippet: string, + public readonly name: string, + public readonly langId: string + ) { } @@ -62,16 +71,28 @@ class InsertSnippetAction extends EditorAction { const quickOpenService = accessor.get(IQuickOpenService); const {lineNumber, column} = editor.getPosition(); - let {name, langId} = NameAndLangId.fromArg(arg); + let {snippet, name, langId} = Args.fromUser(arg); - let languageId: LanguageId; - if (langId) { - languageId = modeService.getLanguageIdentifier(langId).iid; - } else { - languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column); - } return new TPromise((resolve, reject) => { + + if (snippet) { + return resolve({ + codeSnippet: snippet, + description: undefined, + name: undefined, + owner: undefined, + prefix: undefined + }); + } + + let languageId: LanguageId; + if (langId) { + languageId = modeService.getLanguageIdentifier(langId).iid; + } else { + languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column); + } + if (name) { // take selected snippet Registry.as(Extensions.Snippets).visitSnippets(languageId, snippet => { 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/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 575df57988c..729428af7ba 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -50,7 +50,7 @@ class Menu implements IMenu { this._extensionService.onReady().then(_ => { const menuItems = MenuRegistry.getMenuItems(id); - const keysFilter: { [key: string]: boolean } = Object.create(null); + const keysFilter = new Set(); let group: MenuItemGroup; menuItems.sort(Menu._compareMenuItems); @@ -71,7 +71,7 @@ class Menu implements IMenu { // subscribe to context changes this._disposables.push(this._contextKeyService.onDidChangeContext(keys => { for (let k of keys) { - if (keysFilter[k]) { + if (keysFilter.has(k)) { this._onDidChange.fire(); return; } @@ -110,10 +110,10 @@ class Menu implements IMenu { return result; } - private static _fillInKbExprKeys(exp: ContextKeyExpr, set: { [k: string]: boolean }): void { + private static _fillInKbExprKeys(exp: ContextKeyExpr, set: Set): void { if (exp) { for (let key of exp.keys()) { - set[key] = true; + set.add(key); } } } diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 2960eaa7f15..7ad7588bbca 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -51,7 +51,7 @@ function isCommand(thing: any): thing is ICommand { export const CommandsRegistry: ICommandRegistry = new class implements ICommandRegistry { - private _commands: { [id: string]: ICommand | ICommand[] } = Object.create(null); + private _commands = new Map(); registerCommand(id: string, commandOrDesc: ICommandHandler | ICommand): IDisposable { @@ -86,18 +86,18 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR } // find a place to store the command - const commandOrArray = this._commands[id]; + const commandOrArray = this._commands.get(id); if (commandOrArray === void 0) { - this._commands[id] = command; + this._commands.set(id, command); } else if (Array.isArray(commandOrArray)) { commandOrArray.unshift(command); } else { - this._commands[id] = [command, commandOrArray]; + this._commands.set(id, [command, commandOrArray]); } return { dispose: () => { - const commandOrArray = this._commands[id]; + const commandOrArray = this._commands.get(id); if (Array.isArray(commandOrArray)) { // remove from array, remove array // if last element removed @@ -105,19 +105,19 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR if (idx >= 0) { commandOrArray.splice(idx, 1); if (commandOrArray.length === 0) { - delete this._commands[id]; + this._commands.delete(id); } } } else if (isCommand(commandOrArray)) { // remove from map - delete this._commands[id]; + this._commands.delete(id); } } }; } getCommand(id: string): ICommand { - const commandOrArray = this._commands[id]; + const commandOrArray = this._commands.get(id); if (Array.isArray(commandOrArray)) { return commandOrArray[0]; } else { @@ -127,9 +127,9 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR getCommands(): ICommandsMap { const result: ICommandsMap = Object.create(null); - for (let id in this._commands) { - result[id] = this.getCommand(id); - } + this._commands.forEach((value, key) => { + result[key] = this.getCommand(key); + }); return result; } }; @@ -139,4 +139,4 @@ export const NullCommandService: ICommandService = { executeCommand() { return TPromise.as(undefined); } -}; \ No newline at end of file +}; 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/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 13e55e6f3fb..caa91b82bb4 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -148,7 +148,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/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/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 4516bdd249a..6d8ad575681 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -466,7 +466,7 @@ function createExtensionPathIndex(extensionService: ExtHostExtensionService): TP function defineAPI(factory: IExtensionApiFactory, extensionPaths: TrieMap): void { // each extension is meant to get its own api implementation - const extApiImpl: { [id: string]: typeof vscode } = Object.create(null); + const extApiImpl = new Map(); let defaultApiImpl: typeof vscode; const node_module = require.__$__nodeRequire('module'); @@ -479,9 +479,10 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TrieMap(); private _proxy: MainThreadCommandsShape; private _extHostEditors: ExtHostEditors; private _converter: CommandsConverter; @@ -52,15 +52,15 @@ export class ExtHostCommands extends ExtHostCommandsShape { throw new Error('invalid id'); } - if (this._commands[id]) { + if (this._commands.has(id)) { throw new Error('command with id already exists'); } - this._commands[id] = { callback, thisArg, description }; + this._commands.set(id, { callback, thisArg, description }); this._proxy.$registerCommand(id); return new extHostTypes.Disposable(() => { - if (delete this._commands[id]) { + if (this._commands.delete(id)) { this._proxy.$unregisterCommand(id); } }); @@ -68,7 +68,7 @@ export class ExtHostCommands extends ExtHostCommandsShape { executeCommand(id: string, ...args: any[]): Thenable { - if (this._commands[id]) { + if (this._commands.has(id)) { // we stay inside the extension host and support // to pass any kind of parameters around return this.$executeContributedCommand(id, ...args); @@ -97,7 +97,7 @@ export class ExtHostCommands extends ExtHostCommandsShape { } $executeContributedCommand(id: string, ...args: any[]): Thenable { - let command = this._commands[id]; + let command = this._commands.get(id); if (!command) { return TPromise.wrapError(`Contributed command '${id}' does not exist.`); } @@ -139,12 +139,12 @@ export class ExtHostCommands extends ExtHostCommandsShape { $getContributedCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }> { const result: { [id: string]: string | ICommandHandlerDescription } = Object.create(null); - for (let id in this._commands) { - let {description} = this._commands[id]; + this._commands.forEach((command, id) => { + let {description} = command; if (description) { result[id] = description; } - } + }); return TPromise.as(result); } } @@ -212,4 +212,4 @@ export class CommandsConverter { return this._commands.executeCommand(actualCmd.command, ...actualCmd.arguments); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostDiagnostics.ts b/src/vs/workbench/api/node/extHostDiagnostics.ts index f4a44d77ed7..4285991a3a8 100644 --- a/src/vs/workbench/api/node/extHostDiagnostics.ts +++ b/src/vs/workbench/api/node/extHostDiagnostics.ts @@ -15,13 +15,13 @@ import { DiagnosticSeverity } from './extHostTypes'; export class DiagnosticCollection implements vscode.DiagnosticCollection { - private static _maxDiagnosticsPerFile: number = 250; + private static readonly _maxDiagnosticsPerFile: number = 250; + + private readonly _name: string; - private _name: string; private _proxy: MainThreadDiagnosticsShape; - private _isDisposed = false; - private _data: { [uri: string]: vscode.Diagnostic[] } = Object.create(null); + private _data = new Map(); constructor(name: string, proxy: MainThreadDiagnosticsShape) { this._name = name; @@ -66,7 +66,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { } // update single row - this._data[first.toString()] = diagnostics; + this._data.set(first.toString(), diagnostics); toSync = [first]; } else if (Array.isArray(first)) { @@ -83,19 +83,19 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { for (const {tuple} of sortedTuples) { const [uri, diagnostics] = tuple; if (!lastUri || uri.toString() !== lastUri.toString()) { - if (lastUri && this._data[lastUri.toString()].length === 0) { - delete this._data[lastUri.toString()]; + if (lastUri && this._data.get(lastUri.toString()).length === 0) { + this._data.delete(lastUri.toString()); } lastUri = uri; toSync.push(uri); - this._data[uri.toString()] = []; + this._data.set(uri.toString(), []); } if (!diagnostics) { // [Uri, undefined] means clear this - this._data[uri.toString()].length = 0; + this._data.get(uri.toString()).length = 0; } else { - this._data[uri.toString()].push(...diagnostics); + this._data.get(uri.toString()).push(...diagnostics); } } } @@ -104,7 +104,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { const entries: [URI, IMarkerData[]][] = []; for (let uri of toSync) { let marker: IMarkerData[]; - let diagnostics = this._data[uri.toString()]; + let diagnostics = this._data.get(uri.toString()); if (diagnostics) { // no more than 250 diagnostics per file @@ -144,27 +144,27 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { delete(uri: vscode.Uri): void { this._checkDisposed(); - delete this._data[uri.toString()]; + this._data.delete(uri.toString()); this._proxy.$changeMany(this.name, [[uri, undefined]]); } clear(): void { this._checkDisposed(); - this._data = Object.create(null); + this._data.clear(); this._proxy.$clear(this.name); } forEach(callback: (uri: URI, diagnostics: vscode.Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void { this._checkDisposed(); - for (let key in this._data) { + this._data.forEach((value, key) => { let uri = URI.parse(key); callback.apply(thisArg, [uri, this.get(uri), this]); - } + }); } get(uri: URI): vscode.Diagnostic[] { this._checkDisposed(); - let result = this._data[uri.toString()]; + let result = this._data.get(uri.toString()); if (Array.isArray(result)) { return Object.freeze(result.slice(0)); } @@ -172,7 +172,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { has(uri: URI): boolean { this._checkDisposed(); - return Array.isArray(this._data[uri.toString()]); + return Array.isArray(this._data.get(uri.toString())); } private _checkDisposed() { diff --git a/src/vs/workbench/api/node/extHostDocuments.ts b/src/vs/workbench/api/node/extHostDocuments.ts index 7b9eaf556da..6b18fc8b951 100644 --- a/src/vs/workbench/api/node/extHostDocuments.ts +++ b/src/vs/workbench/api/node/extHostDocuments.ts @@ -20,16 +20,14 @@ import { asWinJsPromise } from 'vs/base/common/async'; import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { MainContext, MainThreadDocumentsShape, ExtHostDocumentsShape, IModelAddedData } from './extHost.protocol'; -const _modeId2WordDefinition: { - [modeId: string]: RegExp; -} = Object.create(null); +const _modeId2WordDefinition = new Map(); function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { - _modeId2WordDefinition[modeId] = wordDefinition; + _modeId2WordDefinition.set(modeId, wordDefinition); } function getWordDefinitionFor(modeId: string): RegExp { - return _modeId2WordDefinition[modeId]; + return _modeId2WordDefinition.get(modeId); } export class ExtHostDocuments extends ExtHostDocumentsShape { @@ -48,9 +46,9 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { private _onDidSaveDocumentEventEmitter: Emitter; public onDidSaveDocument: Event; - private _documentData: { [modelUri: string]: ExtHostDocumentData; }; - private _documentLoader: { [modelUri: string]: TPromise }; - private _documentContentProviders: { [handle: number]: vscode.TextDocumentContentProvider; }; + private _documentData = new Map(); + private _documentLoader = new Map>(); + private _documentContentProviders = new Map(); private _proxy: MainThreadDocumentsShape; @@ -69,17 +67,11 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { this._onDidSaveDocumentEventEmitter = new Emitter(); this.onDidSaveDocument = this._onDidSaveDocumentEventEmitter.event; - - this._documentData = Object.create(null); - this._documentLoader = Object.create(null); - this._documentContentProviders = Object.create(null); } public getAllDocumentData(): ExtHostDocumentData[] { const result: ExtHostDocumentData[] = []; - for (let key in this._documentData) { - result.push(this._documentData[key]); - } + this._documentData.forEach(data => result.push(data)); return result; } @@ -87,7 +79,7 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { if (!resource) { return; } - const data = this._documentData[resource.toString()]; + const data = this._documentData.get(resource.toString()); if (data) { return data; } @@ -95,21 +87,21 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { public ensureDocumentData(uri: URI): TPromise { - let cached = this._documentData[uri.toString()]; + let cached = this._documentData.get(uri.toString()); if (cached) { return TPromise.as(cached); } - let promise = this._documentLoader[uri.toString()]; + let promise = this._documentLoader.get(uri.toString()); if (!promise) { promise = this._proxy.$tryOpenDocument(uri).then(() => { - delete this._documentLoader[uri.toString()]; - return this._documentData[uri.toString()]; + this._documentLoader.delete(uri.toString()); + return this._documentData.get(uri.toString()); }, err => { - delete this._documentLoader[uri.toString()]; + this._documentLoader.delete(uri.toString()); return TPromise.wrapError(err); }); - this._documentLoader[uri.toString()] = promise; + this._documentLoader.set(uri.toString(), promise); } return promise; @@ -122,13 +114,13 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { const handle = ExtHostDocuments._handlePool++; - this._documentContentProviders[handle] = provider; + this._documentContentProviders.set(handle, provider); this._proxy.$registerTextContentProvider(handle, scheme); let subscription: IDisposable; if (typeof provider.onDidChange === 'function') { subscription = provider.onDidChange(uri => { - if (this._documentData[uri.toString()]) { + if (this._documentData.has(uri.toString())) { this.$provideTextDocumentContent(handle, uri).then(value => { return this._proxy.$onVirtualDocumentChange(uri, value); }, onUnexpectedError); @@ -136,7 +128,7 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { }); } return new Disposable(() => { - if (delete this._documentContentProviders[handle]) { + if (this._documentContentProviders.delete(handle)) { this._proxy.$unregisterTextContentProvider(handle); } if (subscription) { @@ -147,7 +139,7 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { } $provideTextDocumentContent(handle: number, uri: URI): TPromise { - const provider = this._documentContentProviders[handle]; + const provider = this._documentContentProviders.get(handle); if (!provider) { return TPromise.wrapError(`unsupported uri-scheme: ${uri.scheme}`); } @@ -157,15 +149,15 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { public $acceptModelAdd(initData: IModelAddedData): void { let data = new ExtHostDocumentData(this._proxy, initData.url, initData.value.lines, initData.value.EOL, initData.modeId, initData.versionId, initData.isDirty); let key = data.document.uri.toString(); - if (this._documentData[key]) { + if (this._documentData.has(key)) { throw new Error('Document `' + key + '` already exists.'); } - this._documentData[key] = data; + this._documentData.set(key, data); this._onDidAddDocumentEventEmitter.fire(data.document); } public $acceptModelModeChanged(strURL: string, oldModeId: string, newModeId: string): void { - let data = this._documentData[strURL]; + let data = this._documentData.get(strURL); // Treat a mode change as a remove + add @@ -175,33 +167,33 @@ export class ExtHostDocuments extends ExtHostDocumentsShape { } public $acceptModelSaved(strURL: string): void { - let data = this._documentData[strURL]; + let data = this._documentData.get(strURL); data._acceptIsDirty(false); this._onDidSaveDocumentEventEmitter.fire(data.document); } public $acceptModelDirty(strURL: string): void { - let document = this._documentData[strURL]; + let document = this._documentData.get(strURL); document._acceptIsDirty(true); } public $acceptModelReverted(strURL: string): void { - let document = this._documentData[strURL]; + let document = this._documentData.get(strURL); document._acceptIsDirty(false); } public $acceptModelRemoved(strURL: string): void { - if (!this._documentData[strURL]) { + if (!this._documentData.has(strURL)) { throw new Error('Document `' + strURL + '` does not exist.'); } - let data = this._documentData[strURL]; - delete this._documentData[strURL]; + let data = this._documentData.get(strURL); + this._documentData.delete(strURL); this._onDidRemoveDocumentEventEmitter.fire(data.document); data.dispose(); } public $acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[], isDirty: boolean): void { - let data = this._documentData[strURL]; + let data = this._documentData.get(strURL); data._acceptIsDirty(isDirty); data.onEvents(events); this._onDidChangeDocumentEventEmitter.fire({ diff --git a/src/vs/workbench/api/node/extHostEditors.ts b/src/vs/workbench/api/node/extHostEditors.ts index 7556f5a818a..7f1b0e57cd5 100644 --- a/src/vs/workbench/api/node/extHostEditors.ts +++ b/src/vs/workbench/api/node/extHostEditors.ts @@ -30,7 +30,7 @@ export class ExtHostEditors extends ExtHostEditorsShape { public onDidChangeTextEditorViewColumn: Event; private _onDidChangeTextEditorViewColumn: Emitter; - private _editors: { [id: string]: ExtHostTextEditor }; + private _editors: Map; private _proxy: MainThreadEditorsShape; private _onDidChangeActiveTextEditor: Emitter; private _onDidChangeVisibleTextEditors: Emitter; @@ -56,17 +56,17 @@ export class ExtHostEditors extends ExtHostEditorsShape { this._proxy = threadService.get(MainContext.MainThreadEditors); this._onDidChangeActiveTextEditor = new Emitter(); this._onDidChangeVisibleTextEditors = new Emitter(); - this._editors = Object.create(null); + this._editors = new Map(); this._visibleEditorIds = []; } getActiveTextEditor(): vscode.TextEditor { - return this._editors[this._activeEditorId]; + return this._editors.get(this._activeEditorId); } getVisibleTextEditors(): vscode.TextEditor[] { - return this._visibleEditorIds.map(id => this._editors[id]); + return this._visibleEditorIds.map(id => this._editors.get(id)); } get onDidChangeActiveTextEditor(): Event { @@ -79,7 +79,7 @@ export class ExtHostEditors extends ExtHostEditorsShape { showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): TPromise { return this._proxy.$tryShowTextDocument(document.uri, TypeConverters.fromViewColumn(column), preserveFocus).then(id => { - let editor = this._editors[id]; + let editor = this._editors.get(id); if (editor) { return editor; } else { @@ -97,11 +97,11 @@ export class ExtHostEditors extends ExtHostEditorsShape { $acceptTextEditorAdd(data: ITextEditorAddData): void { let document = this._extHostDocuments.getDocumentData(data.document); let newEditor = new ExtHostTextEditor(this._proxy, data.id, document, data.selections.map(TypeConverters.toSelection), data.options, TypeConverters.toViewColumn(data.editorPosition)); - this._editors[data.id] = newEditor; + this._editors.set(data.id, newEditor); } $acceptOptionsChanged(id: string, opts: IResolvedTextEditorConfiguration): void { - let editor = this._editors[id]; + let editor = this._editors.get(id); editor._acceptOptions(opts); this._onDidChangeTextEditorOptions.fire({ textEditor: editor, @@ -112,7 +112,7 @@ export class ExtHostEditors extends ExtHostEditorsShape { $acceptSelectionsChanged(id: string, event: ISelectionChangeEvent): void { const kind = TextEditorSelectionChangeKind.fromValue(event.source); const selections = event.selections.map(TypeConverters.toSelection); - const textEditor = this._editors[id]; + const textEditor = this._editors.get(id); textEditor._acceptSelections(selections); this._onDidChangeTextEditorSelection.fire({ textEditor, @@ -145,7 +145,7 @@ export class ExtHostEditors extends ExtHostEditorsShape { $acceptEditorPositionData(data: ITextEditorPositionData): void { for (let id in data) { - let textEditor = this._editors[id]; + let textEditor = this._editors.get(id); let viewColumn = TypeConverters.toViewColumn(data[id]); if (textEditor.viewColumn !== viewColumn) { textEditor._acceptViewColumn(viewColumn); @@ -165,9 +165,9 @@ export class ExtHostEditors extends ExtHostEditorsShape { this.$acceptActiveEditorAndVisibleEditors(this._activeEditorId, newVisibleEditors); } - let editor = this._editors[id]; + let editor = this._editors.get(id); editor.dispose(); - delete this._editors[id]; + this._editors.delete(id); } } diff --git a/src/vs/workbench/api/node/extHostHeapService.ts b/src/vs/workbench/api/node/extHostHeapService.ts index c993ae4ecba..62c38b8528a 100644 --- a/src/vs/workbench/api/node/extHostHeapService.ts +++ b/src/vs/workbench/api/node/extHostHeapService.ts @@ -10,20 +10,20 @@ export class ExtHostHeapService extends ExtHostHeapServiceShape { private static _idPool = 0; - private _data: { [n: number]: any } = Object.create(null); + private _data = new Map(); keep(obj: any): number { const id = ExtHostHeapService._idPool++; - this._data[id] = obj; + this._data.set(id, obj); return id; } delete(id: number): boolean { - return this._data[id]; + return this._data.delete(id); } get(id: number): T { - return this._data[id]; + return this._data.get(id); } $onGarbageCollection(ids: number[]): void { @@ -31,4 +31,4 @@ export class ExtHostHeapService extends ExtHostHeapServiceShape { this.delete(id); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 038eb9e0138..38706f8abb2 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -624,7 +624,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { private _commands: ExtHostCommands; private _heapService: ExtHostHeapService; private _diagnostics: ExtHostDiagnostics; - private _adapter: { [handle: number]: Adapter } = Object.create(null); + private _adapter = new Map(); constructor( threadService: IThreadService, @@ -643,7 +643,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { private _createDisposable(handle: number): Disposable { return new Disposable(() => { - delete this._adapter[handle]; + this._adapter.delete(handle); this._proxy.$unregister(handle); }); } @@ -653,7 +653,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { } private _withAdapter(handle: number, ctor: { new (...args: any[]): A }, callback: (adapter: A) => TPromise): TPromise { - let adapter = this._adapter[handle]; + let adapter = this._adapter.get(handle); if (!(adapter instanceof ctor)) { return TPromise.wrapError(new Error('no adapter found')); } @@ -664,7 +664,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new OutlineAdapter(this._documents, provider); + this._adapter.set(handle, new OutlineAdapter(this._documents, provider)); this._proxy.$registerOutlineSupport(handle, selector); return this._createDisposable(handle); } @@ -679,7 +679,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { const handle = this._nextHandle(); const eventHandle = typeof provider.onDidChangeCodeLenses === 'function' ? this._nextHandle() : undefined; - this._adapter[handle] = new CodeLensAdapter(this._documents, this._commands.converter, this._heapService, provider); + this._adapter.set(handle, new CodeLensAdapter(this._documents, this._commands.converter, this._heapService, provider)); this._proxy.$registerCodeLensSupport(handle, selector, eventHandle); let result = this._createDisposable(handle); @@ -703,7 +703,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new DefinitionAdapter(this._documents, provider); + this._adapter.set(handle, new DefinitionAdapter(this._documents, provider)); this._proxy.$registerDeclaractionSupport(handle, selector); return this._createDisposable(handle); } @@ -716,7 +716,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new HoverAdapter(this._documents, provider); + this._adapter.set(handle, new HoverAdapter(this._documents, provider)); this._proxy.$registerHoverProvider(handle, selector); return this._createDisposable(handle); } @@ -729,7 +729,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new DocumentHighlightAdapter(this._documents, provider); + this._adapter.set(handle, new DocumentHighlightAdapter(this._documents, provider)); this._proxy.$registerDocumentHighlightProvider(handle, selector); return this._createDisposable(handle); } @@ -742,7 +742,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new ReferenceAdapter(this._documents, provider); + this._adapter.set(handle, new ReferenceAdapter(this._documents, provider)); this._proxy.$registerReferenceSupport(handle, selector); return this._createDisposable(handle); } @@ -755,7 +755,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new QuickFixAdapter(this._documents, this._commands.converter, this._diagnostics, this._heapService, provider); + this._adapter.set(handle, new QuickFixAdapter(this._documents, this._commands.converter, this._diagnostics, this._heapService, provider)); this._proxy.$registerQuickFixSupport(handle, selector); return this._createDisposable(handle); } @@ -768,7 +768,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerDocumentFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new DocumentFormattingAdapter(this._documents, provider); + this._adapter.set(handle, new DocumentFormattingAdapter(this._documents, provider)); this._proxy.$registerDocumentFormattingSupport(handle, selector); return this._createDisposable(handle); } @@ -779,7 +779,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerDocumentRangeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new RangeFormattingAdapter(this._documents, provider); + this._adapter.set(handle, new RangeFormattingAdapter(this._documents, provider)); this._proxy.$registerRangeFormattingSupport(handle, selector); return this._createDisposable(handle); } @@ -790,7 +790,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, triggerCharacters: string[]): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new OnTypeFormattingAdapter(this._documents, provider); + this._adapter.set(handle, new OnTypeFormattingAdapter(this._documents, provider)); this._proxy.$registerOnTypeFormattingSupport(handle, selector, triggerCharacters); return this._createDisposable(handle); } @@ -803,7 +803,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new NavigateTypeAdapter(provider, this._heapService); + this._adapter.set(handle, new NavigateTypeAdapter(provider, this._heapService)); this._proxy.$registerNavigateTypeSupport(handle); return this._createDisposable(handle); } @@ -820,7 +820,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new RenameAdapter(this._documents, provider); + this._adapter.set(handle, new RenameAdapter(this._documents, provider)); this._proxy.$registerRenameSupport(handle, selector); return this._createDisposable(handle); } @@ -833,7 +833,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new SuggestAdapter(this._documents, this._commands.converter, this._heapService, provider); + this._adapter.set(handle, new SuggestAdapter(this._documents, this._commands.converter, this._heapService, provider)); this._proxy.$registerSuggestSupport(handle, selector, triggerCharacters); return this._createDisposable(handle); } @@ -850,7 +850,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, triggerCharacters: string[]): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new SignatureHelpAdapter(this._documents, provider); + this._adapter.set(handle, new SignatureHelpAdapter(this._documents, provider)); this._proxy.$registerSignatureHelpProvider(handle, selector, triggerCharacters); return this._createDisposable(handle); } @@ -863,7 +863,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new LinkProviderAdapter(this._documents, provider); + this._adapter.set(handle, new LinkProviderAdapter(this._documents, provider)); this._proxy.$registerDocumentLinkProvider(handle, selector); return this._createDisposable(handle); } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index cc30c8ef508..e7b26461e60 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -465,7 +465,7 @@ export class Uri extends URI { } export class WorkspaceEdit { private _values: [Uri, TextEdit[]][] = []; - private _index: { [uri: string]: number } = Object.create(null); + private _index = new Map(); replace(uri: Uri, range: Range, newText: string): void { let edit = new TextEdit(range, newText); @@ -486,21 +486,21 @@ export class WorkspaceEdit { } has(uri: Uri): boolean { - return typeof this._index[uri.toString()] !== 'undefined'; + return this._index.has(uri.toString()); } set(uri: Uri, edits: TextEdit[]): void { - let idx = this._index[uri.toString()]; + const idx = this._index.get(uri.toString()); if (typeof idx === 'undefined') { let newLen = this._values.push([uri, edits]); - this._index[uri.toString()] = newLen - 1; + this._index.set(uri.toString(), newLen - 1); } else { this._values[idx][1] = edits; } } get(uri: Uri): TextEdit[] { - let idx = this._index[uri.toString()]; + let idx = this._index.get(uri.toString()); return typeof idx !== 'undefined' && this._values[idx][1]; } 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 de1702e0b72..dfbd18ee310 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -180,31 +180,23 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe } if (path) { - const basename = paths.basename(path); - const dotSegments = basename.split('.'); + const basename = cssEscape(paths.basename(path).toLowerCase()); // Folders if (isFolder) { - if (basename) { - classes.push(`${basename.toLowerCase()}-name-folder-icon`); - } + classes.push(`${basename}-name-folder-icon`); } // Files else { // Name - const name = dotSegments[0]; // file.txt => "file", .dockerfile => "", file.some.txt => "file" - if (name) { - classes.push(`${cssEscape(name.toLowerCase())}-name-file-icon`); - } + classes.push(`${basename}-name-file-icon`); // Extension(s) - const extensions = dotSegments.splice(1); - if (extensions.length > 0) { - for (let i = 0; i < extensions.length; i++) { - classes.push(`${cssEscape(extensions.slice(i).join('.').toLowerCase())}-ext-file-icon`); // add each combination of all found extensions if more than one - } + const dotSegments = basename.split('.'); + for (let i = 1; i < dotSegments.length; i++) { + classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one } // Configured Language @@ -215,7 +207,6 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe } } } - return classes; } 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 c32873989b3..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'; @@ -210,7 +210,6 @@ function handleCommandDeprecations(): void { 'workbench.files.action.reopenClosedFile': 'workbench.action.reopenClosedEditor', 'workbench.files.action.workingFilesPicker': 'workbench.action.showAllEditors', 'workbench.action.cycleEditor': 'workbench.action.navigateEditorGroups', - 'workbench.action.terminal.focus': 'workbench.action.focusPanel', 'workbench.action.showEditorsInLeftGroup': 'workbench.action.showEditorsInFirstGroup', 'workbench.action.showEditorsInCenterGroup': 'workbench.action.showEditorsInSecondGroup', 'workbench.action.showEditorsInRightGroup': 'workbench.action.showEditorsInThirdGroup', diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index 7df18064577..ce92c4c1a14 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, 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..d9747208de9 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -15,15 +15,12 @@ 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 { 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'; @@ -51,11 +48,8 @@ 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 ) { @@ -65,30 +59,10 @@ export abstract class BaseTextEditor extends BaseEditor { this.toUnbind.push(themeService.onDidColorThemeChange(_ => this.handleConfigurationChangeEvent())); } - 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 { if (this.isVisible()) { this.applyConfiguration(configuration); 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..4717f2ec35c 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, 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..10f15341b35 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -588,8 +588,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/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/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/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 23a4ebba24f..0562753bed2 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -71,7 +71,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 { @@ -349,7 +349,7 @@ 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})`; } } 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/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/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/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/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..8006157ba6c 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -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()); 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/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..892ba87928d 100644 --- a/src/vs/workbench/parts/preferences/browser/media/preferences.css +++ b/src/vs/workbench/parts/preferences/browser/media/preferences.css @@ -147,19 +147,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..bc7a744c14f 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); } @@ -153,9 +150,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 +172,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 +192,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 +208,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 +217,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 +262,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 +283,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 +317,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 +355,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 +407,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 +442,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 +671,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 +693,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 +725,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 +752,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 +902,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 +916,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..f3e53ff1119 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); @@ -135,7 +132,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic 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..8bbd3b3911d 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts @@ -344,4 +344,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/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..748b1904ff2 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,57 @@ 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.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.setSelection([prev], eventPayload); + } + 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..9847b83b1f0 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -503,7 +503,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); } 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 8f2d8e84874..55ccb3ff1a3 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'; @@ -29,7 +28,6 @@ export class SnippetsTracker implements workbenchExt.IWorkbenchContribution { private fileWatchDelayer: async.ThrottledDelayer; constructor( - @IFileService private fileService: IFileService, @ILifecycleService private lifecycleService: ILifecycleService, @IModeService private modeService: IModeService, @IEnvironmentService environmentService: IEnvironmentService diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 6121cc1c58c..2e56d1240c9 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; @@ -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) { @@ -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,7 +372,7 @@ export class TerminalInstance implements ITerminalInstance { }); setTimeout(() => { this._isLaunching = false; - }, TerminalInstance.LAUNCHING_DURATION); + }, LAUNCHING_DURATION); } // TODO: This should be private/protected 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..9da33446228 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, 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/themeService.ts b/src/vs/workbench/services/themes/electron-browser/themeService.ts index 8fbb2cdb58e..423f92fa1b5 100644 --- a/src/vs/workbench/services/themes/electron-browser/themeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/themeService.ts @@ -442,7 +442,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; @@ -553,7 +553,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`); @@ -564,11 +564,10 @@ function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, ic let fileNames = associations.fileNames; if (fileNames) { for (let fileName in fileNames) { - let selectors = []; - let segments = fileName.toLowerCase().split('.'); - if (segments[0]) { - selectors.push(`.${escapeCSS(segments[0])}-name-file-icon`); - } + let selectors: string[] = []; + fileName = fileName.toLowerCase(); + selectors.push(`.${escapeCSS(fileName)}-name-file-icon`); + let segments = fileName.split('.'); for (let i = 1; i < segments.length; i++) { selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); } 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/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 {