diff --git a/.github/classifier.yml b/.github/classifier.yml index 348242d802a..c2666691dc8 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -9,14 +9,14 @@ debug: [ isidorn ], editor: [], editor-brackets: [], - editor-clipboard: [ rebornix ], + editor-clipboard: [], editor-colors: [], - editor-contrib: [ rebornix ], + editor-contrib: [], editor-core: [], - editor-find-widget: [ rebornix ], + editor-find-widget: [], editor-folding: [], - editor-ime: [ rebornix ], - editor-indentation: [ rebornix ], + editor-ime: [], + editor-indentation: [], editor-input: [], editor-minimap: [], editor-multicursor: [], @@ -26,14 +26,14 @@ extensions: [], git: [ joaomoreno ], hot-exit: [ Tyriar ], - html: [], + html: [ aeschli ], i18n: [], install-update: [], integrated-terminal: [ Tyriar ], javascript: [ mjbvz ], json: [], languages basic: [], - markdown: [], + markdown: [ mjbvz ], merge-conflict: [ chrmarti ], perf-profile: [], php: [ roblourens ], diff --git a/.github/new_release.yml b/.github/new_release.yml index 3877cb6f8a2..902078aa79c 100644 --- a/.github/new_release.yml +++ b/.github/new_release.yml @@ -1,5 +1,5 @@ { newReleaseLabel: 'new release', - newReleases: ['1.17'], + newReleases: ['1.17.2'], perform: true } \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index a04880dd308..0854813e23f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ cache: notifications: email: false + webhooks: + - http://vscode-test-probot.westus.cloudapp.azure.com:3450/travis/notifications addons: apt: diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 9f7088b945e..beb618c5195 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -45,8 +45,8 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.17.18' }, - { name: 'ms-vscode.node-debug2', version: '1.18.1' } + { name: 'ms-vscode.node-debug', version: '1.18.1' }, + { name: 'ms-vscode.node-debug2', version: '1.18.3' } ]; const excludedExtensions = [ diff --git a/build/lib/watch/index.js b/build/lib/watch/index.js index bed579ec075..93d9babc2de 100644 --- a/build/lib/watch/index.js +++ b/build/lib/watch/index.js @@ -19,14 +19,15 @@ function handleDeletions() { let watch = void 0; -if (!process.env['VSCODE_USE_LEGACY_WATCH']) { - try { - watch = require('./watch-nsfw'); - } catch (err) { - console.warn('Could not load our cross platform file watcher: ' + err.toString()); - console.warn('Falling back to our platform specific watcher...'); - } -} +// Disabled due to https://github.com/Microsoft/vscode/issues/36214 +// if (!process.env['VSCODE_USE_LEGACY_WATCH']) { +// try { +// watch = require('./watch-nsfw'); +// } catch (err) { +// console.warn('Could not load our cross platform file watcher: ' + err.toString()); +// console.warn('Falling back to our platform specific watcher...'); +// } +// } if (!watch) { watch = process.platform === 'win32' ? require('./watch-win32') : require('gulp-watch'); diff --git a/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json b/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json index 86441c11dd8..68e1bc4e83b 100644 --- a/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json +++ b/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-coffee-script/commit/da81e3f537ccbbb70e542fa5af79583eb58ec50b", + "version": "https://github.com/atom/language-coffee-script/commit/8873cbc4e2f3b790603cbe7102d60f41fc82f726", "scopeName": "source.coffee", "name": "CoffeeScript", "fileTypes": [ @@ -535,13 +535,13 @@ "arguments": { "patterns": [ { - "begin": "(?=(@|@?[\\w$]+|[=-]>|\\-\\d|\\[|{|\"|'))|\\(", + "begin": "\\(", "beginCaptures": { "0": { "name": "punctuation.definition.arguments.begin.bracket.round.coffee" } }, - "end": "\\)|(?=\\s*(?|\\-\\d|\\[|{|\"|'))", + "end": "(?=\\s*(?|\\-\\d|\\[|\\{|\"|'))|(?=\\())", + "begin": "(@)?([\\w$]+)(?=\\()", "beginCaptures": { "1": { "name": "variable.other.readwrite.instance.coffee" @@ -600,27 +610,56 @@ "2": { "patterns": [ { - "match": "(?x)\n\\b(isNaN|isFinite|eval|uneval|parseInt|parseFloat|decodeURI|\ndecodeURIComponent|encodeURI|encodeURIComponent|escape|unescape|\nrequire|set(Interval|Timeout)|clear(Interval|Timeout))\\b", - "name": "support.function.coffee" - }, - { - "match": "[a-zA-Z_$][\\w$]*", - "name": "entity.name.function.coffee" - }, - { - "match": "\\d[\\w$]*", - "name": "invalid.illegal.identifier.coffee" + "include": "#function_names" } ] } }, - "end": "(?<=\\))|(?=\\s*(?|\\-\\d|\\[|{|\"|')))", + "beginCaptures": { + "1": { + "name": "variable.other.readwrite.instance.coffee" + }, + "2": { + "patterns": [ + { + "include": "#function_names" + } + ] + } + }, + "end": "(?=\\s*(?|\\-\\d|\\[|\\{|\"|'))|(?=\\())", + "begin": "(?:(\\.)|(::))\\s*([\\w$]+)\\s*(?=\\()", "beginCaptures": { "1": { "name": "punctuation.separator.method.period.coffee" @@ -724,35 +763,67 @@ "3": { "patterns": [ { - "match": "(?x)\n\\bon(Rowsinserted|Rowsdelete|Rowenter|Rowexit|Resize|Resizestart|Resizeend|Reset|\nReadystatechange|Mouseout|Mouseover|Mousedown|Mouseup|Mousemove|\nBefore(cut|deactivate|unload|update|paste|print|editfocus|activate)|\nBlur|Scrolltop|Submit|Select|Selectstart|Selectionchange|Hover|Help|\nChange|Contextmenu|Controlselect|Cut|Cellchange|Clock|Close|Deactivate|\nDatasetchanged|Datasetcomplete|Dataavailable|Drop|Drag|Dragstart|Dragover|\nDragdrop|Dragenter|Dragend|Dragleave|Dblclick|Unload|Paste|Propertychange|Error|\nErrorupdate|Keydown|Keyup|Keypress|Focus|Load|Activate|Afterupdate|Afterprint|Abort)\\b", - "name": "support.function.event-handler.coffee" - }, - { - "match": "(?x)\n\\b(shift|showModelessDialog|showModalDialog|showHelp|scroll|scrollX|scrollByPages|\nscrollByLines|scrollY|scrollTo|stop|strike|sizeToContent|sidebar|signText|sort|\nsup|sub|substr|substring|splice|split|send|set(Milliseconds|Seconds|Minutes|Hours|\nMonth|Year|FullYear|Date|UTC(Milliseconds|Seconds|Minutes|Hours|Month|FullYear|Date)|\nTime|Hotkeys|Cursor|ZOptions|Active|Resizable|RequestHeader)|search|slice|\nsavePreferences|small|home|handleEvent|navigate|char|charCodeAt|charAt|concat|\ncontextual|confirm|compile|clear|captureEvents|call|createStyleSheet|createPopup|\ncreateEventObject|to(GMTString|UTCString|String|Source|UpperCase|LowerCase|LocaleString)|\ntest|taint|taintEnabled|indexOf|italics|disableExternalCapture|dump|detachEvent|unshift|\nuntaint|unwatch|updateCommands|join|javaEnabled|pop|push|plugins.refresh|paddings|parse|\nprint|prompt|preference|enableExternalCapture|exec|execScript|valueOf|UTC|find|file|\nfileModifiedDate|fileSize|fileCreatedDate|fileUpdatedDate|fixed|fontsize|fontcolor|\nforward|fromCharCode|watch|link|load|lastIndexOf|anchor|attachEvent|atob|apply|alert|\nabort|routeEvents|resize|resizeBy|resizeTo|recalc|returnValue|replace|reverse|reload|\nreleaseCapture|releaseEvents|go|get(Milliseconds|Seconds|Minutes|Hours|Month|Day|Year|FullYear|\nTime|Date|TimezoneOffset|UTC(Milliseconds|Seconds|Minutes|Hours|Day|Month|FullYear|Date)|\nAttention|Selection|ResponseHeader|AllResponseHeaders)|moveBy|moveBelow|moveTo|\nmoveToAbsolute|moveAbove|mergeAttributes|match|margins|btoa|big|bold|borderWidths|blink|back)\\b", - "name": "support.function.coffee" - }, - { - "match": "(?x)\n\\b(acceptNode|add|addEventListener|addTextTrack|adoptNode|after|animate|append|\nappendChild|appendData|before|blur|canPlayType|captureStream|\ncaretPositionFromPoint|caretRangeFromPoint|checkValidity|clear|click|\ncloneContents|cloneNode|cloneRange|close|closest|collapse|\ncompareBoundaryPoints|compareDocumentPosition|comparePoint|contains|\nconvertPointFromNode|convertQuadFromNode|convertRectFromNode|createAttribute|\ncreateAttributeNS|createCaption|createCDATASection|createComment|\ncreateContextualFragment|createDocument|createDocumentFragment|\ncreateDocumentType|createElement|createElementNS|createEntityReference|\ncreateEvent|createExpression|createHTMLDocument|createNodeIterator|\ncreateNSResolver|createProcessingInstruction|createRange|createShadowRoot|\ncreateTBody|createTextNode|createTFoot|createTHead|createTreeWalker|delete|\ndeleteCaption|deleteCell|deleteContents|deleteData|deleteRow|deleteTFoot|\ndeleteTHead|detach|disconnect|dispatchEvent|elementFromPoint|elementsFromPoint|\nenableStyleSheetsForSet|entries|evaluate|execCommand|exitFullscreen|\nexitPointerLock|expand|extractContents|fastSeek|firstChild|focus|forEach|get|\ngetAll|getAnimations|getAttribute|getAttributeNames|getAttributeNode|\ngetAttributeNodeNS|getAttributeNS|getBoundingClientRect|getBoxQuads|\ngetClientRects|getContext|getDestinationInsertionPoints|getElementById|\ngetElementsByClassName|getElementsByName|getElementsByTagName|\ngetElementsByTagNameNS|getItem|getNamedItem|getSelection|getStartDate|\ngetVideoPlaybackQuality|has|hasAttribute|hasAttributeNS|hasAttributes|\nhasChildNodes|hasFeature|hasFocus|importNode|initEvent|insertAdjacentElement|\ninsertAdjacentHTML|insertAdjacentText|insertBefore|insertCell|insertData|\ninsertNode|insertRow|intersectsNode|isDefaultNamespace|isEqualNode|\nisPointInRange|isSameNode|item|key|keys|lastChild|load|lookupNamespaceURI|\nlookupPrefix|matches|move|moveAttribute|moveAttributeNode|moveChild|\nmoveNamedItem|namedItem|nextNode|nextSibling|normalize|observe|open|\nparentNode|pause|play|postMessage|prepend|preventDefault|previousNode|\npreviousSibling|probablySupportsContext|queryCommandEnabled|\nqueryCommandIndeterm|queryCommandState|queryCommandSupported|queryCommandValue|\nquerySelector|querySelectorAll|registerContentHandler|registerElement|\nregisterProtocolHandler|releaseCapture|releaseEvents|remove|removeAttribute|\nremoveAttributeNode|removeAttributeNS|removeChild|removeEventListener|\nremoveItem|replace|replaceChild|replaceData|replaceWith|reportValidity|\nrequestFullscreen|requestPointerLock|reset|scroll|scrollBy|scrollIntoView|\nscrollTo|seekToNextFrame|select|selectNode|selectNodeContents|set|setAttribute|\nsetAttributeNode|setAttributeNodeNS|setAttributeNS|setCapture|\nsetCustomValidity|setEnd|setEndAfter|setEndBefore|setItem|setNamedItem|\nsetRangeText|setSelectionRange|setSinkId|setStart|setStartAfter|setStartBefore|\nslice|splitText|stepDown|stepUp|stopImmediatePropagation|stopPropagation|\nsubmit|substringData|supports|surroundContents|takeRecords|terminate|toBlob|\ntoDataURL|toggle|toString|values|write|writeln)\\b", - "name": "support.function.dom.coffee" - }, - { - "match": "[a-zA-Z_$][\\w$]*", - "name": "entity.name.function.coffee" - }, - { - "match": "\\d[\\w$]*", - "name": "invalid.illegal.identifier.coffee" + "include": "#method_names" } ] } }, - "end": "(?<=\\))|(?=\\s*(?|\\-\\d|\\[|{|\"|')))", + "beginCaptures": { + "1": { + "name": "punctuation.separator.method.period.coffee" + }, + "2": { + "name": "keyword.operator.prototype.coffee" + }, + "3": { + "patterns": [ + { + "include": "#method_names" + } + ] + } + }, + "end": "(?=\\s*(? { const completions: vscode.CompletionItem[] = []; - completions.push(this.newSimpleCompletionItem('${activeEditorShort}', range, localize('activeEditorShort', "e.g. the file name (myFile.txt)"))); - completions.push(this.newSimpleCompletionItem('${activeEditorMedium}', range, localize('activeEditorMedium', "e.g. the path of the file relative to the workspace folder (myFolder/myFile.txt)"))); - completions.push(this.newSimpleCompletionItem('${activeEditorLong}', range, localize('activeEditorLong', "e.g. the full path of the file (/Users/Development/myProject/myFolder/myFile.txt)"))); + completions.push(this.newSimpleCompletionItem('${activeEditorShort}', range, localize('activeEditorShort', "the file name (e.g. myFile.txt)"))); + completions.push(this.newSimpleCompletionItem('${activeEditorMedium}', range, localize('activeEditorMedium', "the path of the file relative to the workspace folder (e.g. myFolder/myFile.txt)"))); + completions.push(this.newSimpleCompletionItem('${activeEditorLong}', range, localize('activeEditorLong', "the full path of the file (e.g. /Users/Development/myProject/myFolder/myFile.txt)"))); completions.push(this.newSimpleCompletionItem('${rootName}', range, localize('rootName', "name of the workspace (e.g. myFolder or myWorkspace)"))); completions.push(this.newSimpleCompletionItem('${rootPath}', range, localize('rootPath', "file path of the workspace (e.g. /Users/Development/myWorkspace)"))); completions.push(this.newSimpleCompletionItem('${folderName}', range, localize('folderName', "name of the workspace folder the file is contained in (e.g. myFolder)"))); diff --git a/extensions/cpp/language-configuration.json b/extensions/cpp/language-configuration.json index 2888f3edea9..a4f6c85aeab 100644 --- a/extensions/cpp/language-configuration.json +++ b/extensions/cpp/language-configuration.json @@ -28,8 +28,8 @@ }, "folding": { "markers": { - "start": "^\\s*#pragma\\s+region", - "end": "^\\s*#pragma\\s+endregion" + "start": "^\\s*#pragma\\s+region\\b", + "end": "^\\s*#pragma\\s+endregion\\b" } } } \ No newline at end of file diff --git a/extensions/csharp/language-configuration.json b/extensions/csharp/language-configuration.json index 17f3fb0caf6..88107685266 100644 --- a/extensions/csharp/language-configuration.json +++ b/extensions/csharp/language-configuration.json @@ -26,8 +26,8 @@ ], "folding": { "markers": { - "start": "^\\s*#region", - "end": "^\\s*#endregion" + "start": "^\\s*#region\\b", + "end": "^\\s*#endregion\\b" } } } \ No newline at end of file diff --git a/extensions/css/client/src/cssMain.ts b/extensions/css/client/src/cssMain.ts index 6b43c68252f..e86425b2d81 100644 --- a/extensions/css/client/src/cssMain.ts +++ b/extensions/css/client/src/cssMain.ts @@ -66,10 +66,10 @@ export function activate(context: ExtensionContext) { }); }); }, - provideColorPresentations(document: TextDocument, colorInfo: ColorInformation): ColorPresentation[] | Thenable { + provideColorPresentations(color: Color, context): ColorPresentation[] | Thenable { let params: ColorPresentationParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - colorInfo: { range: client.code2ProtocolConverter.asRange(colorInfo.range), color: colorInfo.color } + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(context.document), + colorInfo: { range: client.code2ProtocolConverter.asRange(context.range), color } }; return client.sendRequest(ColorPresentationRequest.type, params).then(presentations => { return presentations.map(p => { diff --git a/extensions/css/npm-shrinkwrap.json b/extensions/css/npm-shrinkwrap.json index 0cb7377ce6d..2a022a155e1 100644 --- a/extensions/css/npm-shrinkwrap.json +++ b/extensions/css/npm-shrinkwrap.json @@ -2,26 +2,25 @@ "name": "css", "version": "0.1.0", "dependencies": { - "vscode-jsonrpc": { - "version": "3.4.0", - "from": "vscode-jsonrpc@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-jsonrpc@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0-next.1.tgz" }, "vscode-languageclient": { - "version": "3.4.2", + "version": "3.5.0-next.3", "from": "vscode-languageclient@next", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.4.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.5.0-next.3.tgz" }, "vscode-languageserver-protocol": { - "version": "3.4.2", - "from": "vscode-languageserver-protocol@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.2.tgz" + "version": "3.5.0-next.3", + "from": "vscode-languageserver-protocol@>=3.5.0-next.3 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0-next.3.tgz" }, "vscode-languageserver-types": { - "version": "3.4.0", - "from": "vscode-languageserver-types@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-languageserver-types@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0-next.1.tgz" }, "vscode-nls": { "version": "2.0.2", diff --git a/extensions/css/package.json b/extensions/css/package.json index c9b0c82f1de..d124a1430f7 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -720,8 +720,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^3.4.2", - "vscode-languageserver-protocol": "^3.4.2", + "vscode-languageclient": "^3.5.0-next.3", "vscode-nls": "^2.0.2" }, "devDependencies": { diff --git a/extensions/css/server/npm-shrinkwrap.json b/extensions/css/server/npm-shrinkwrap.json index 1b647cda8c7..8325a987e91 100644 --- a/extensions/css/server/npm-shrinkwrap.json +++ b/extensions/css/server/npm-shrinkwrap.json @@ -3,29 +3,29 @@ "version": "1.0.0", "dependencies": { "vscode-css-languageservice": { - "version": "2.1.9", + "version": "2.1.10", "from": "vscode-css-languageservice@next", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.9.tgz" + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.10.tgz" }, "vscode-jsonrpc": { - "version": "3.4.0", - "from": "vscode-jsonrpc@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-jsonrpc@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0-next.1.tgz" }, "vscode-languageserver": { - "version": "3.4.2", + "version": "3.5.0-next.2", "from": "vscode-languageserver@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.4.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.0-next.2.tgz" }, "vscode-languageserver-protocol": { - "version": "3.4.2", - "from": "vscode-languageserver-protocol@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.2.tgz" + "version": "3.5.0-next.3", + "from": "vscode-languageserver-protocol@>=3.5.0-next.2 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0-next.3.tgz" }, "vscode-languageserver-types": { - "version": "3.4.0", - "from": "vscode-languageserver-types@>=3.3.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-languageserver-types@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0-next.1.tgz" }, "vscode-nls": { "version": "2.0.2", diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index 841cb4d9cf4..1feda7d7f9d 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -8,9 +8,8 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^2.1.9", - "vscode-languageserver": "^3.4.2", - "vscode-languageserver-protocol": "^3.4.2" + "vscode-css-languageservice": "^2.1.10", + "vscode-languageserver": "^3.5.0-next.2" }, "devDependencies": { "@types/node": "7.0.43" diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index b768d3c1d90..9a8c21d3892 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -130,6 +130,31 @@ "type": "string", "default": " ", "description": "%emmetPreferencesStylusBetween%" + }, + "bem.elementSeparator": { + "type": "string", + "default": "__", + "description": "%emmetPreferencesBemElementSeparator%" + }, + "bem.modifierSeparator": { + "type": "string", + "default": "_", + "description": "%emmetPreferencesBemModifierSeparator%" + }, + "filter.commentBefore": { + "type": "string", + "default": "", + "description": "%emmetPreferencesFilterCommentBefore%" + }, + "filter.commentAfter": { + "type": "string", + "default": "\n", + "description": "%emmetPreferencesFilterCommentAfter%" + }, + "filter.commentTrigger": { + "type": "array", + "default": ["id", "class"], + "description": "%emmetPreferencesFilterCommentTrigger%" } } }, diff --git a/extensions/emmet/package.nls.json b/extensions/emmet/package.nls.json index 4d63301d0ac..07ffacf4b44 100644 --- a/extensions/emmet/package.nls.json +++ b/extensions/emmet/package.nls.json @@ -23,13 +23,13 @@ "command.incrementNumberByTen": "Increment by 10", "command.decrementNumberByTen": "Decrement by 10", "emmetSyntaxProfiles": "Define profile for specified syntax or use your own profile with specific rules.", - "emmetExclude": "An array of languages where emmet abbreviations should not be expanded.", - "emmetExtensionsPath": "Path to a folder containing emmet profiles and snippets.'", - "emmetShowExpandedAbbreviation": "Shows expanded emmet abbreviations as suggestions.\nThe option \"inMarkupAndStylesheetFilesOnly\" applies to html, haml, jade, slim, xml, xsl, css, scss, sass, less and stylus.\nThe option \"always\" applies to all parts of the file regardless of markup/css.", - "emmetShowAbbreviationSuggestions": "Shows possible emmet abbreviations as suggestions. Not applicable in stylesheets or when emmet.showExpandedAbbreviation is set to \"never\".", - "emmetIncludeLanguages": "Enable emmet abbreviations in languages that are not supported by default. Add a mapping here between the language and emmet supported language.\n Eg: {\"vue-html\": \"html\", \"javascript\": \"javascriptreact\"}", - "emmetVariables": "Variables to be used in emmet snippets", - "emmetTriggerExpansionOnTab": "When enabled, emmet abbreviations are expanded when pressing TAB.", + "emmetExclude": "An array of languages where Emmet abbreviations should not be expanded.", + "emmetExtensionsPath": "Path to a folder containing Emmet profiles and snippets.'", + "emmetShowExpandedAbbreviation": "Shows expanded Emmet abbreviations as suggestions.\nThe option \"inMarkupAndStylesheetFilesOnly\" applies to html, haml, jade, slim, xml, xsl, css, scss, sass, less and stylus.\nThe option \"always\" applies to all parts of the file regardless of markup/css.", + "emmetShowAbbreviationSuggestions": "Shows possible Emmet abbreviations as suggestions. Not applicable in stylesheets or when emmet.showExpandedAbbreviation is set to \"never\".", + "emmetIncludeLanguages": "Enable Emmet abbreviations in languages that are not supported by default. Add a mapping here between the language and emmet supported language.\n Eg: {\"vue-html\": \"html\", \"javascript\": \"javascriptreact\"}", + "emmetVariables": "Variables to be used in Emmet snippets", + "emmetTriggerExpansionOnTab": "When enabled, Emmet abbreviations are expanded when pressing TAB.", "emmetPreferences": "Preferences used to modify behavior of some actions and resolvers of Emmet.", "emmetPreferencesIntUnit": "Default unit for integer values", "emmetPreferencesFloatUnit": "Default unit for float values", @@ -39,5 +39,10 @@ "emmetPreferencesCssBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations", "emmetPreferencesSassBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations in Sass files", "emmetPreferencesStylusBetween": "Symbol to be placed at the between CSS property and value when expanding CSS abbreviations in Stylus files", - "emmetShowSuggestionsAsSnippets": "If true, then emmet suggestions will show up as snippets allowing you to order them as per editor.snippetSuggestions setting." -} \ No newline at end of file + "emmetShowSuggestionsAsSnippets": "If true, then Emmet suggestions will show up as snippets allowing you to order them as per editor.snippetSuggestions setting.", + "emmetPreferencesBemElementSeparator": "Element separator used for classes when using the BEM filter", + "emmetPreferencesBemModifierSeparator": "Modifier separator used for classes when using the BEM filter", + "emmetPreferencesFilterCommentBefore": "A definition of comment that should be placed before matched element when comment filter is applied.", + "emmetPreferencesFilterCommentAfter": "A definition of comment that should be placed after matched element when comment filter is applied.", + "emmetPreferencesFilterCommentTrigger": "A comma-separated list of attribute names that should exist in abbreviation for the comment filter to be applied" +} diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index c0d3758abdb..2cf7e0501de 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -139,7 +139,14 @@ export function expandEmmetAbbreviation(args): Thenable { return [new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character), abbreviation, filter]; }; - editor.selections.forEach(selection => { + let selectionsInReverseOrder = editor.selections.slice(0); + selectionsInReverseOrder.sort((a, b) => { + var posA = a.isReversed ? a.anchor : a.active; + var posB = b.isReversed ? b.anchor : b.active; + return posA.compareTo(posB) * -1; + }); + + selectionsInReverseOrder.forEach(selection => { let position = selection.isReversed ? selection.anchor : selection.active; let [rangeToReplace, abbreviation, filter] = getAbbreviation(editor.document, selection, position, syntax); if (!rangeToReplace) { diff --git a/extensions/fsharp/language-configuration.json b/extensions/fsharp/language-configuration.json index 9cdb97d7912..e4affc8deaa 100644 --- a/extensions/fsharp/language-configuration.json +++ b/extensions/fsharp/language-configuration.json @@ -24,8 +24,8 @@ "folding": { "offSide": true, "markers": { - "start": "^\\s*//\\s*#region|^\\s*\\(\\*\\s*#region(.*)\\*\\)", - "end": "^\\s*//\\s*#endregion|^\\s*\\(\\*\\s*#endregion\\s*\\*\\)" + "start": "^\\s*//\\s*#region\\b|^\\s*\\(\\*\\s*#region(.*)\\*\\)", + "end": "^\\s*//\\s*#endregion\\b|^\\s*\\(\\*\\s*#endregion\\s*\\*\\)" } } } diff --git a/extensions/git/package.json b/extensions/git/package.json index 3dbdb6604bf..a69c16b2429 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -101,6 +101,24 @@ "title": "%command.revertSelectedRanges%", "category": "Git" }, + { + "command": "git.stageChange", + "title": "%command.stageChange%", + "category": "Git", + "icon": { + "light": "resources/icons/light/stage.svg", + "dark": "resources/icons/dark/stage.svg" + } + }, + { + "command": "git.revertChange", + "title": "%command.revertChange%", + "category": "Git", + "icon": { + "light": "resources/icons/light/clean.svg", + "dark": "resources/icons/dark/clean.svg" + } + }, { "command": "git.unstage", "title": "%command.unstage%", @@ -722,6 +740,16 @@ "group": "2_git@3", "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" } + ], + "scm/change/title": [ + { + "command": "git.stageChange", + "when": "config.git.enabled && originalResourceScheme == git" + }, + { + "command": "git.revertChange", + "when": "config.git.enabled && originalResourceScheme == git" + } ] }, "configuration": { @@ -803,7 +831,36 @@ "default": false } } - } + }, + "colors": [ + { + "id": "git.color.modified", + "description": "Color for modified resources", + "defaults": { + "light": "#D58809", + "dark": "#E2C08D", + "highContrast": "#E2C08D" + } + }, + { + "id": "git.color.untracked", + "description": "Color for untracked resources", + "defaults": { + "light": "#00B333", + "dark": "#73C991", + "highContrast": "#73C991" + } + }, + { + "id": "git.color.ignored", + "description": "Color for ignored resources", + "defaults": { + "light": "#8E8E90", + "dark": "#A7A8A9", + "highContrast": "#A7A8A9" + } + } + ] }, "dependencies": { "byline": "^5.0.0", @@ -816,4 +873,4 @@ "@types/node": "7.0.43", "mocha": "^3.2.0" } -} \ No newline at end of file +} diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index da2c9210fd1..d191adfadf4 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -10,6 +10,8 @@ "command.stageAll": "Stage All Changes", "command.stageSelectedRanges": "Stage Selected Ranges", "command.revertSelectedRanges": "Revert Selected Ranges", + "command.stageChange": "Stage Change", + "command.revertChange": "Revert Change", "command.unstage": "Unstage Changes", "command.unstageAll": "Unstage All Changes", "command.unstageSelectedRanges": "Unstage Selected Ranges", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index c41d60d2fbb..2988ad609a7 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation } from 'vscode'; +import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository'; import { Model } from './model'; @@ -177,7 +177,9 @@ export class CommandCenter { const activeTextEditor = window.activeTextEditor; - if (preserveSelection && activeTextEditor && activeTextEditor.document.uri.toString() === right.toString()) { + // Check if active text editor has same path as other editor. we cannot compare via + // URI.toString() here because the schemas can be different. Instead we just go by path. + if (preserveSelection && activeTextEditor && activeTextEditor.document.uri.path === right.path) { opts.selection = activeTextEditor.selection; } @@ -414,11 +416,13 @@ export class CommandCenter { for (const uri of uris) { const opts: TextDocumentShowOptions = { preserveFocus, - preview: preview, + preview, viewColumn: ViewColumn.Active }; - if (activeTextEditor && activeTextEditor.document.uri.toString() === uri.toString()) { + // Check if active text editor has same path as other editor. we cannot compare via + // URI.toString() here because the schemas can be different. Instead we just go by path. + if (activeTextEditor && activeTextEditor.document.uri.path === uri.path) { opts.selection = activeTextEditor.selection; } @@ -556,14 +560,39 @@ export class CommandCenter { await repository.add([]); } + @command('git.stageChange') + async stageChange(uri: Uri, changes: LineChange[], index: number): Promise { + const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0]; + + if (!textEditor) { + return; + } + + await this._stageChanges(textEditor, [changes[index]]); + } + @command('git.stageSelectedRanges', { diff: true }) - async stageSelectedRanges(diffs: LineChange[]): Promise { + async stageSelectedChanges(changes: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { return; } + const modifiedDocument = textEditor.document; + const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); + const selectedChanges = changes + .map(diff => selectedLines.reduce((result, range) => result || intersectDiffWithRange(modifiedDocument, diff, range), null)) + .filter(d => !!d) as LineChange[]; + + if (!selectedChanges.length) { + return; + } + + await this._stageChanges(textEditor, selectedChanges); + } + + private async _stageChanges(textEditor: TextEditor, changes: LineChange[]): Promise { const modifiedDocument = textEditor.document; const modifiedUri = modifiedDocument.uri; @@ -573,28 +602,48 @@ export class CommandCenter { const originalUri = toGitUri(modifiedUri, '~'); const originalDocument = await workspace.openTextDocument(originalUri); - const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); - const selectedDiffs = diffs - .map(diff => selectedLines.reduce((result, range) => result || intersectDiffWithRange(modifiedDocument, diff, range), null)) - .filter(d => !!d) as LineChange[]; - - if (!selectedDiffs.length) { - return; - } - - const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs); + const result = applyLineChanges(originalDocument, modifiedDocument, changes); await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result)); } + @command('git.revertChange') + async revertChange(uri: Uri, changes: LineChange[], index: number): Promise { + const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0]; + + if (!textEditor) { + return; + } + + await this._revertChanges(textEditor, [...changes.slice(0, index), ...changes.slice(index + 1)]); + } + @command('git.revertSelectedRanges', { diff: true }) - async revertSelectedRanges(diffs: LineChange[]): Promise { + async revertSelectedRanges(changes: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { return; } + const modifiedDocument = textEditor.document; + const selections = textEditor.selections; + const selectedChanges = changes.filter(change => { + const modifiedRange = change.modifiedEndLineNumber === 0 + ? new Range(modifiedDocument.lineAt(change.modifiedStartLineNumber - 1).range.end, modifiedDocument.lineAt(change.modifiedStartLineNumber).range.start) + : new Range(modifiedDocument.lineAt(change.modifiedStartLineNumber - 1).range.start, modifiedDocument.lineAt(change.modifiedEndLineNumber - 1).range.end); + + return selections.every(selection => !selection.intersection(modifiedRange)); + }); + + if (selectedChanges.length === changes.length) { + return; + } + + await this._revertChanges(textEditor, selectedChanges); + } + + private async _revertChanges(textEditor: TextEditor, changes: LineChange[]): Promise { const modifiedDocument = textEditor.document; const modifiedUri = modifiedDocument.uri; @@ -604,19 +653,6 @@ export class CommandCenter { const originalUri = toGitUri(modifiedUri, '~'); const originalDocument = await workspace.openTextDocument(originalUri); - const selections = textEditor.selections; - const selectedDiffs = diffs.filter(diff => { - const modifiedRange = diff.modifiedEndLineNumber === 0 - ? new Range(modifiedDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, modifiedDocument.lineAt(diff.modifiedStartLineNumber).range.start) - : new Range(modifiedDocument.lineAt(diff.modifiedStartLineNumber - 1).range.start, modifiedDocument.lineAt(diff.modifiedEndLineNumber - 1).range.end); - - return selections.every(selection => !selection.intersection(modifiedRange)); - }); - - if (selectedDiffs.length === diffs.length) { - return; - } - const basename = path.basename(modifiedUri.fsPath); const message = localize('confirm revert', "Are you sure you want to revert the selected changes in {0}?", basename); const yes = localize('revert', "Revert Changes"); @@ -626,10 +662,11 @@ export class CommandCenter { return; } - const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs); + const result = applyLineChanges(originalDocument, modifiedDocument, changes); const edit = new WorkspaceEdit(); edit.replace(modifiedUri, new Range(new Position(0, 0), modifiedDocument.lineAt(modifiedDocument.lineCount - 1).range.end), result); workspace.applyEdit(edit); + await modifiedDocument.save(); } @command('git.unstage') diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index 78852ec6685..4363c80af8e 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -7,8 +7,8 @@ import { workspace, Uri, Disposable, Event, EventEmitter, window } from 'vscode'; import { debounce, throttle } from './decorators'; -import { fromGitUri } from './uri'; -import { Model, ModelChangeEvent } from './model'; +import { fromGitUri, toGitUri } from './uri'; +import { Model, ModelChangeEvent, OriginalResourceChangeEvent } from './model'; import { filterEvent, eventToPromise } from './util'; interface CacheRow { @@ -25,8 +25,8 @@ const FIVE_MINUTES = 1000 * 60 * 5; export class GitContentProvider { - private onDidChangeEmitter = new EventEmitter(); - get onDidChange(): Event { return this.onDidChangeEmitter.event; } + private _onDidChange = new EventEmitter(); + get onDidChange(): Event { return this._onDidChange.event; } private changedRepositoryRoots = new Set(); private cache: Cache = Object.create(null); @@ -35,6 +35,7 @@ export class GitContentProvider { constructor(private model: Model) { this.disposables.push( model.onDidChangeRepository(this.onDidChangeRepository, this), + model.onDidChangeOriginalResource(this.onDidChangeOriginalResource, this), workspace.registerTextDocumentContentProvider('git', this) ); @@ -46,6 +47,14 @@ export class GitContentProvider { this.eventuallyFireChangeEvents(); } + private onDidChangeOriginalResource({ uri }: OriginalResourceChangeEvent): void { + if (uri.scheme !== 'file') { + return; + } + + this._onDidChange.fire(toGitUri(uri, '', true)); + } + @debounce(1100) private eventuallyFireChangeEvents(): void { this.fireChangeEvents(); @@ -64,7 +73,7 @@ export class GitContentProvider { for (const root of this.changedRepositoryRoots) { if (fsPath.startsWith(root)) { - this.onDidChangeEmitter.fire(uri); + this._onDidChange.fire(uri); return; } } @@ -82,7 +91,7 @@ export class GitContentProvider { const cacheKey = uri.toString(); const timestamp = new Date().getTime(); - const cacheValue = { uri, timestamp }; + const cacheValue: CacheRow = { uri, timestamp }; this.cache[cacheKey] = cacheValue; @@ -108,7 +117,10 @@ export class GitContentProvider { Object.keys(this.cache).forEach(key => { const row = this.cache[key]; - const isOpen = window.visibleTextEditors.some(e => e.document.toString() === row.uri.toString()); + const { path } = fromGitUri(row.uri); + const isOpen = workspace.textDocuments + .filter(d => d.uri.scheme === 'file') + .some(d => d.uri.fsPath === path); if (isOpen || now - row.timestamp < THREE_MINUTES) { cache[row.uri.toString()] = row; diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts new file mode 100644 index 00000000000..813a035b519 --- /dev/null +++ b/extensions/git/src/decorationProvider.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { window, workspace, Uri, Disposable, Event, EventEmitter, DecorationData, DecorationProvider, ThemeColor } from 'vscode'; +import { Repository, GitResourceGroup } from './repository'; +import { Model } from './model'; +import { debounce } from './decorators'; +import { filterEvent } from './util'; + +class GitIgnoreDecorationProvider implements DecorationProvider { + + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; + + private checkIgnoreQueue = new Map void, reject: (err: any) => void }>(); + private disposables: Disposable[] = []; + + constructor(private repository: Repository) { + this.disposables.push( + window.registerDecorationProvider(this), + filterEvent(workspace.onDidSaveTextDocument, e => e.fileName.endsWith('.gitignore'))(_ => this._onDidChangeDecorations.fire()) + //todo@joh -> events when the ignore status actually changes, not only when the file changes + ); + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + this.checkIgnoreQueue.clear(); + } + + provideDecoration(uri: Uri): Promise { + return new Promise((resolve, reject) => { + this.checkIgnoreQueue.set(uri.fsPath, { resolve, reject }); + this.checkIgnoreSoon(); + }).then(ignored => { + if (ignored) { + return { + priority: 3, + color: new ThemeColor('git.color.ignored') + }; + } + }); + } + + @debounce(500) + private checkIgnoreSoon(): void { + const queue = new Map(this.checkIgnoreQueue.entries()); + this.checkIgnoreQueue.clear(); + this.repository.checkIgnore([...queue.keys()]).then(ignoreSet => { + for (const [key, value] of queue.entries()) { + value.resolve(ignoreSet.has(key)); + } + }, err => { + console.error(err); + for (const [, value] of queue.entries()) { + value.reject(err); + } + }); + } +} + +class GitDecorationProvider implements DecorationProvider { + + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; + + private disposables: Disposable[] = []; + private decorations = new Map(); + + constructor(private repository: Repository) { + this.disposables.push( + window.registerDecorationProvider(this), + repository.onDidRunOperation(this.onDidRunOperation, this) + ); + } + + private onDidRunOperation(): void { + let newDecorations = new Map(); + this.collectDecorationData(this.repository.indexGroup, newDecorations); + this.collectDecorationData(this.repository.workingTreeGroup, newDecorations); + + let uris: Uri[] = []; + newDecorations.forEach((value, uriString) => { + if (this.decorations.has(uriString)) { + this.decorations.delete(uriString); + } else { + uris.push(Uri.parse(uriString)); + } + }); + this.decorations.forEach((value, uriString) => { + uris.push(Uri.parse(uriString)); + }); + this.decorations = newDecorations; + this._onDidChangeDecorations.fire(uris); + } + + private collectDecorationData(group: GitResourceGroup, bucket: Map): void { + group.resourceStates.forEach(r => { + if (r.resourceDecoration) { + bucket.set(r.original.toString(), r.resourceDecoration); + } + }); + } + + provideDecoration(uri: Uri): DecorationData | undefined { + return this.decorations.get(uri.toString()); + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} + + +export class GitDecorations { + + private disposables: Disposable[] = []; + private providers = new Map(); + + constructor(private model: Model) { + this.disposables.push( + model.onDidOpenRepository(this.onDidOpenRepository, this), + model.onDidCloseRepository(this.onDidCloseRepository, this) + ); + model.repositories.forEach(this.onDidOpenRepository, this); + } + + private onDidOpenRepository(repository: Repository): void { + const provider = new GitDecorationProvider(repository); + const ignoreProvider = new GitIgnoreDecorationProvider(repository); + this.providers.set(repository, Disposable.from(provider, ignoreProvider)); + } + + private onDidCloseRepository(repository: Repository): void { + const provider = this.providers.get(repository); + if (provider) { + provider.dispose(); + this.providers.delete(repository); + } + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + this.providers.forEach(value => value.dispose); + this.providers.clear(); + } +} diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index bc9ac5398a8..d0f69123a04 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -13,7 +13,6 @@ import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import { assign, uniqBy, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp } from './util'; -const readdir = denodeify(fs.readdir); const readfile = denodeify(fs.readFile); export interface IGit { @@ -118,26 +117,11 @@ function findSystemGitWin32(base: string): Promise { return findSpecificGit(path.join(base, 'Git', 'cmd', 'git.exe')); } -function findGitHubGitWin32(): Promise { - const github = path.join(process.env['LOCALAPPDATA'], 'GitHub'); - - return readdir(github).then(children => { - const git = children.filter(child => /^PortableGit/.test(child))[0]; - - if (!git) { - return Promise.reject('Not found'); - } - - return findSpecificGit(path.join(github, git, 'cmd', 'git.exe')); - }); -} - function findGitWin32(): Promise { return findSystemGitWin32(process.env['ProgramW6432']) .then(void 0, () => findSystemGitWin32(process.env['ProgramFiles(x86)'])) .then(void 0, () => findSystemGitWin32(process.env['ProgramFiles'])) - .then(void 0, () => findSpecificGit('git')) - .then(void 0, () => findGitHubGitWin32()); + .then(void 0, () => findSpecificGit('git')); } export function findGit(hint: string | undefined): Promise { diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index acc30eeb81d..b090495be71 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -12,6 +12,7 @@ import { findGit, Git, IGit } from './git'; import { Model } from './model'; import { CommandCenter } from './commands'; import { GitContentProvider } from './contentProvider'; +import { GitDecorations } from './decorationProvider'; import { Askpass } from './askpass'; import { toDisposable } from './util'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -54,6 +55,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi disposables.push( new CommandCenter(git, model, outputChannel, telemetryReporter), new GitContentProvider(model), + new GitDecorations(model) ); await checkGitVersion(info); @@ -93,4 +95,4 @@ async function checkGitVersion(info: IGit): Promise { } else if (choice === neverShowAgain) { await config.update('ignoreLegacyWarning', true, true); } -} \ No newline at end of file +} diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 93a809a20fe..be87561ae9a 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -35,6 +35,11 @@ export interface ModelChangeEvent { uri: Uri; } +export interface OriginalResourceChangeEvent { + repository: Repository; + uri: Uri; +} + interface OpenRepository extends Disposable { repository: Repository; } @@ -54,6 +59,9 @@ export class Model { private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; + private _onDidChangeOriginalResource = new EventEmitter(); + readonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event; + private openRepositories: OpenRepository[] = []; get repositories(): Repository[] { return this.openRepositories.map(r => r.repository); } @@ -217,10 +225,14 @@ export class Model { const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed); const disappearListener = onDidDisappearRepository(() => dispose()); const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); + const originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri })); + const dispose = () => { disappearListener.dispose(); changeListener.dispose(); + originalResourceChangeListener.dispose(); repository.dispose(); + this.openRepositories = this.openRepositories.filter(e => e !== openRepository); this._onDidCloseRepository.fire(repository); }; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index da154939744..6e16bf9d44c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,8 +5,8 @@ 'use strict'; -import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; -import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash, RefType } from './git'; +import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData } from 'vscode'; +import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash, RefType, GitError } from './git'; import { anyEvent, filterEvent, eventToPromise, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; import { toGitUri } from './uri'; @@ -180,6 +180,19 @@ export class Resource implements SourceControlResourceState { return { strikeThrough, faded, tooltip, light, dark }; } + get resourceDecoration(): DecorationData | undefined { + const title = this.tooltip; + switch (this.type) { + case Status.UNTRACKED: + return { priority: 1, title, abbreviation: localize('untracked, short', "U"), bubble: true, color: new ThemeColor('git.color.untracked') }; + case Status.INDEX_MODIFIED: + case Status.MODIFIED: + return { priority: 2, title, abbreviation: localize('modified, short', "M"), bubble: true, color: new ThemeColor('git.color.modified') }; + default: + return undefined; + } + } + constructor( private _resourceGroupType: ResourceGroupType, private _resourceUri: Uri, @@ -302,6 +315,9 @@ export class Repository implements Disposable { private _onDidChangeStatus = new EventEmitter(); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + private _onDidChangeOriginalResource = new EventEmitter(); + readonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event; + private _onRunOperation = new EventEmitter(); readonly onRunOperation: Event = this._onRunOperation.event; @@ -447,6 +463,7 @@ export class Repository implements Disposable { async stage(resource: Uri, contents: string): Promise { const relativePath = path.relative(this.repository.root, resource.fsPath).replace(/\\/g, '/'); await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents)); + this._onDidChangeOriginalResource.fire(resource); } async revert(resources: Uri[]): Promise { @@ -627,6 +644,44 @@ export class Repository implements Disposable { }); } + checkIgnore(filePaths: string[]): Promise> { + return this.run(Operation.Ignore, () => { + return new Promise>((resolve, reject) => { + + filePaths = filePaths.filter(filePath => !path.relative(this.root, filePath).startsWith('..')); + + const child = this.repository.stream(['check-ignore', ...filePaths]); + + const onExit = exitCode => { + if (exitCode === 1) { + // nothing ignored + resolve(new Set()); + } else if (exitCode === 0) { + // each line is something ignored + resolve(new Set(data.split('\n'))); + } else { + reject(new GitError({ stdout: data, stderr, exitCode })); + } + }; + + let data = ''; + const onStdoutData = (raw: string) => { + data += raw; + }; + + child.stdout.setEncoding('utf8'); + child.stdout.on('data', onStdoutData); + + let stderr: string = ''; + child.stderr.setEncoding('utf8'); + child.stderr.on('data', raw => stderr += raw); + + child.on('error', reject); + child.on('exit', onExit); + }); + }); + } + private async run(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { if (this.state !== RepositoryState.Idle) { throw new Error('Repository not initialized'); diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index 2dc9899c125..4f355644a04 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -83,10 +83,10 @@ export function activate(context: ExtensionContext) { }); }); }, - provideColorPresentations(document: TextDocument, colorInfo: ColorInformation): Thenable { + provideColorPresentations(color, context): Thenable { let params: ColorPresentationParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - colorInfo: { range: client.code2ProtocolConverter.asRange(colorInfo.range), color: colorInfo.color } + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(context.document), + colorInfo: { range: client.code2ProtocolConverter.asRange(context.range), color } }; return client.sendRequest(ColorPresentationRequest.type, params).then(presentations => { return presentations.map(p => { @@ -175,4 +175,4 @@ function getPackageInfo(context: ExtensionContext): IPackageInfo { }; } return null; -} \ No newline at end of file +} diff --git a/extensions/html/npm-shrinkwrap.json b/extensions/html/npm-shrinkwrap.json index 97c20854f46..ab1071727c3 100644 --- a/extensions/html/npm-shrinkwrap.json +++ b/extensions/html/npm-shrinkwrap.json @@ -7,35 +7,30 @@ "from": "applicationinsights@0.18.0", "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-0.18.0.tgz" }, - "color-convert": { - "version": "0.5.3", - "from": "color-convert@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz" - }, "vscode-extension-telemetry": { "version": "0.0.8", "from": "vscode-extension-telemetry@>=0.0.8 <0.0.9", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz" }, "vscode-jsonrpc": { - "version": "3.4.0", - "from": "vscode-jsonrpc@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-jsonrpc@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0-next.1.tgz" }, "vscode-languageclient": { - "version": "3.4.2", + "version": "3.5.0-next.3", "from": "vscode-languageclient@next", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.4.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.5.0-next.3.tgz" }, "vscode-languageserver-protocol": { - "version": "3.4.2", - "from": "vscode-languageserver-protocol@3.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.2.tgz" + "version": "3.5.0-next.3", + "from": "vscode-languageserver-protocol@>=3.5.0-next.3 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0-next.3.tgz" }, "vscode-languageserver-types": { - "version": "3.4.0", - "from": "vscode-languageserver-types@3.4.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-languageserver-types@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0-next.1.tgz" }, "vscode-nls": { "version": "2.0.2", diff --git a/extensions/html/package.json b/extensions/html/package.json index 7b7fe521038..b197a2c386f 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -17,8 +17,8 @@ "compile": "gulp compile-extension:html-client && gulp compile-extension:html-server", "postinstall": "cd server && npm install", "update-grammar": "node ../../build/npm/update-grammar.js textmate/html.tmbundle Syntaxes/HTML.plist ./syntaxes/html.json", - "install-client-next": "npm install vscode-languageserver-types -f -S && npm install vscode-languageclient@next -f -S", - "install-client-local": "npm install ../../../vscode-languageserver-node/types -f -S && npm install ../../../vscode-languageserver-node/client -f -S" + "install-client-next": "npm install vscode-languageclient@next -f -S", + "install-client-local": "npm install ../../../vscode-languageserver-node/client -f -S" }, "contributes": { "languages": [ @@ -216,9 +216,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.8", - "vscode-languageclient": "^3.4.2", - "vscode-languageserver-protocol": "^3.4.2", - "vscode-languageserver-types": "^3.4.0", + "vscode-languageclient": "^3.5.0-next.3", "vscode-nls": "2.0.2" }, "devDependencies": { diff --git a/extensions/html/server/npm-shrinkwrap.json b/extensions/html/server/npm-shrinkwrap.json index e293aa9b417..3658a9a092c 100644 --- a/extensions/html/server/npm-shrinkwrap.json +++ b/extensions/html/server/npm-shrinkwrap.json @@ -3,9 +3,9 @@ "version": "1.0.0", "dependencies": { "vscode-css-languageservice": { - "version": "2.1.9", + "version": "2.1.10", "from": "vscode-css-languageservice@next", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.9.tgz" + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.10.tgz" }, "vscode-html-languageservice": { "version": "2.0.10", @@ -13,24 +13,24 @@ "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-2.0.10.tgz" }, "vscode-jsonrpc": { - "version": "3.4.0", - "from": "vscode-jsonrpc@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-jsonrpc@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0-next.1.tgz" }, "vscode-languageserver": { - "version": "3.4.2", + "version": "3.5.0-next.2", "from": "vscode-languageserver@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.4.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.0-next.2.tgz" }, "vscode-languageserver-protocol": { - "version": "3.4.2", - "from": "vscode-languageserver-protocol@3.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.2.tgz" + "version": "3.5.0-next.3", + "from": "vscode-languageserver-protocol@>=3.5.0-next.2 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0-next.3.tgz" }, "vscode-languageserver-types": { - "version": "3.4.0", - "from": "vscode-languageserver-types@3.4.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-languageserver-types@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0-next.1.tgz" }, "vscode-nls": { "version": "2.0.2", diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json index 6fdf5cfc678..e0c26108be7 100644 --- a/extensions/html/server/package.json +++ b/extensions/html/server/package.json @@ -8,11 +8,9 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^2.1.9", + "vscode-css-languageservice": "^2.1.10", "vscode-html-languageservice": "^2.0.10", - "vscode-languageserver": "^3.4.2", - "vscode-languageserver-protocol": "^3.4.2", - "vscode-languageserver-types": "^3.4.0", + "vscode-languageserver": "^3.5.0-next.2", "vscode-nls": "^2.0.2", "vscode-uri": "^1.0.1" }, diff --git a/extensions/java/language-configuration.json b/extensions/java/language-configuration.json index 5ae0cec3192..cc45a8a0139 100644 --- a/extensions/java/language-configuration.json +++ b/extensions/java/language-configuration.json @@ -23,5 +23,11 @@ ["\"", "\""], ["'", "'"], ["<", ">"] - ] + ], + "folding": { + "markers": { + "start": "^\\s*//\\s*(#?region\\b)|()" + } + } } diff --git a/extensions/javascript/javascript-language-configuration.json b/extensions/javascript/javascript-language-configuration.json index ad48834d04a..1e8f440a420 100644 --- a/extensions/javascript/javascript-language-configuration.json +++ b/extensions/javascript/javascript-language-configuration.json @@ -27,8 +27,8 @@ ], "folding": { "markers": { - "start": "^\\s*//\\s*#?region", - "end": "^\\s*//\\s*#?endregion" + "start": "^\\s*//\\s*#?region\\b", + "end": "^\\s*//\\s*#?endregion\\b" } } } \ No newline at end of file diff --git a/extensions/javascript/package.json b/extensions/javascript/package.json index 8cc0db74250..866167e1ba6 100644 --- a/extensions/javascript/package.json +++ b/extensions/javascript/package.json @@ -63,11 +63,12 @@ "grammars": [ { "language": "javascriptreact", - "scopeName": "source.js", - "path": "./syntaxes/JavaScript.tmLanguage.json", + "scopeName": "source.js.jsx", + "path": "./syntaxes/JavaScriptReact.tmLanguage.json", "embeddedLanguages": { "meta.tag.js": "jsx-tags", "meta.tag.without-attributes.js": "jsx-tags", + "meta.tag.attributes.js.jsx": "javascriptreact", "meta.embedded.expression.js": "javascriptreact" } }, @@ -78,6 +79,7 @@ "embeddedLanguages": { "meta.tag.js": "jsx-tags", "meta.tag.without-attributes.js": "jsx-tags", + "meta.tag.attributes.js": "javascript", "meta.embedded.expression.js": "javascript" } }, diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 990df29f8b3..7b1ec3d76c5 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/d6ee336bf6047594768a56f955563ba5ce86e7c9", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/824f47ea6e98590ac2e75db5bebdf6eff71421ad", "name": "JavaScript (with React support)", "scopeName": "source.js", "fileTypes": [ @@ -278,7 +278,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js entity.name.function.js" @@ -512,7 +512,7 @@ } }, { - "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -739,7 +739,7 @@ }, { "name": "meta.definition.property.js entity.name.function.js", - "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))" + "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))" }, { "name": "meta.definition.property.js variable.object.property.js", @@ -1002,7 +1002,7 @@ }, { "name": "meta.arrow.js", - "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -1707,8 +1707,7 @@ "include": "#comment" }, { - "comment": "(default|*|name) as alias", - "match": "(?x) (?: \\b(default)\\b | (\\*) | ([_$[:alpha:]][_$[:alnum:]]*)) \\s+\n (as) \\s+ (?: (\\b default \\b | \\*) | ([_$[:alpha:]][_$[:alnum:]]*))", + "match": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=:\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.js" @@ -2043,13 +2039,13 @@ ] }, "function-call": { - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", - "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "patterns": [ { "name": "meta.function-call.js", "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "end": "(?=\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "patterns": [ { "include": "#literal" @@ -2496,7 +2492,7 @@ } }, { - "match": "(?x) (\\.) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "match": "(?x) (\\.) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "captures": { "1": { "name": "punctuation.accessor.js" @@ -2576,7 +2572,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(\\.)\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(\\.)\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.js" @@ -3225,7 +3221,7 @@ "include": "#expression" } ], - "contentName": "meta.embedded.line.tsx" + "contentName": "meta.embedded.line.js" }, "regex": { "patterns": [ @@ -3332,7 +3328,7 @@ "name": "punctuation.definition.group.regexp" }, "1": { - "name": "punctuation.definition.group.capture.regexp" + "name": "punctuation.definition.group.no-capture.regexp" } }, "end": "\\)", @@ -3482,7 +3478,7 @@ } }, "end": "(?=^)", - "contentName": "comment.line.double-slash.tsx" + "contentName": "comment.line.double-slash.js" } ] }, @@ -4020,7 +4016,7 @@ "name": "punctuation.definition.tag.end.js" } }, - "contentName": "meta.jsx.children.tsx", + "contentName": "meta.jsx.children.js", "patterns": [ { "include": "#jsx-children" @@ -4104,6 +4100,7 @@ } }, "end": "(?=[/]?>)", + "contentName": "meta.tag.attributes.js", "patterns": [ { "include": "#comment" @@ -4124,7 +4121,7 @@ } }, "end": "(?=)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "beginCaptures": { + "1": { + "name": "meta.definition.variable.js.jsx entity.name.function.js.jsx" + } + }, + "end": "(?=$|^|[;,=}]|(\\s+(of|in)\\s+))", + "patterns": [ + { + "include": "#var-single-variable-type-annotation" + } + ] + }, + { + "name": "meta.var-single-variable.expr.js.jsx", + "begin": "([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "beginCaptures": { + "1": { + "name": "meta.definition.variable.js.jsx variable.other.constant.js.jsx" + } + }, + "end": "(?=$|^|[;,=}]|(\\s+(of|in)\\s+))", + "patterns": [ + { + "include": "#var-single-variable-type-annotation" + } + ] + }, + { + "name": "meta.var-single-variable.expr.js.jsx", + "begin": "([_$[:alpha:]][_$[:alnum:]]*)", + "beginCaptures": { + "1": { + "name": "meta.definition.variable.js.jsx variable.other.readwrite.js.jsx" + } + }, + "end": "(?=$|^|[;,=}]|(\\s+(of|in)\\s+))", + "patterns": [ + { + "include": "#var-single-variable-type-annotation" + } + ] + } + ] + }, + "var-single-variable-type-annotation": { + "patterns": [ + { + "include": "#type-annotation" + }, + { + "include": "#string" + }, + { + "include": "#comment" + } + ] + }, + "destructuring-variable": { + "patterns": [ + { + "name": "meta.object-binding-pattern-variable.js.jsx", + "begin": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "captures": { + "1": { + "name": "storage.modifier.js.jsx" + }, + "2": { + "name": "keyword.operator.rest.js.jsx" + }, + "3": { + "name": "entity.name.function.js.jsx variable.language.this.js.jsx" + }, + "4": { + "name": "entity.name.function.js.jsx" + }, + "5": { + "name": "keyword.operator.optional.js.jsx" + } + } + }, + { + "match": "(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))" + }, + { + "name": "meta.definition.property.js.jsx variable.object.property.js.jsx", + "match": "[_$[:alpha:]][_$[:alnum:]]*" + }, + { + "name": "keyword.operator.optional.js.jsx", + "match": "\\?" + } + ] + } + ] + }, + "variable-initializer": { + "patterns": [ + { + "begin": "(?)", + "captures": { + "1": { + "name": "storage.modifier.async.js.jsx" + }, + "2": { + "name": "variable.parameter.js.jsx" + } + } + }, + { + "name": "meta.arrow.js.jsx", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "beginCaptures": { + "1": { + "name": "storage.modifier.async.js.jsx" + } + }, + "end": "(?==>|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#type-parameters" + }, + { + "include": "#function-parameters" + }, + { + "include": "#arrow-return-type" + } + ] + }, + { + "name": "meta.arrow.js.jsx", + "begin": "=>", + "beginCaptures": { + "0": { + "name": "storage.type.function.arrow.js.jsx" + } + }, + "end": "(?<=\\}|\\S)(?)|((?!\\{)(?=\\S))", + "patterns": [ + { + "include": "#decl-block" + }, + { + "include": "#expression" + } + ] + } + ] + }, + "indexer-declaration": { + "name": "meta.indexer.declaration.js.jsx", + "begin": "(?:(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "captures": { + "0": { + "name": "meta.object-literal.key.js.jsx" + }, + "1": { + "name": "entity.name.function.js.jsx" + } + } + }, + { + "name": "meta.object.member.js.jsx", + "match": "(?:[_$[:alpha:]][_$[:alnum:]]*)\\s*(?=:)", + "captures": { + "0": { + "name": "meta.object-literal.key.js.jsx" + } + } + }, + { + "name": "meta.object.member.js.jsx", + "begin": "\\.\\.\\.", + "beginCaptures": { + "0": { + "name": "keyword.operator.spread.js.jsx" + } + }, + "end": "(?=,|\\})", + "patterns": [ + { + "include": "#expression" + } + ] + }, + { + "name": "meta.object.member.js.jsx", + "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=,|\\}|$)", + "captures": { + "1": { + "name": "variable.other.readwrite.js.jsx" + } + } + }, + { + "name": "meta.object.member.js.jsx", + "begin": "(?=[_$[:alpha:]][_$[:alnum:]]*\\s*=)", + "end": "(?=,|\\}|$)", + "patterns": [ + { + "include": "#expression" + } + ] + }, + { + "name": "meta.object.member.js.jsx", + "begin": ":", + "beginCaptures": { + "0": { + "name": "meta.object-literal.key.js.jsx punctuation.separator.key-value.js.jsx" + } + }, + "end": "(?=,|\\})", + "patterns": [ + { + "include": "#expression" + } + ] + }, + { + "include": "#punctuation-comma" + } + ] + }, + "ternary-expression": { + "begin": "(\\?)", + "beginCaptures": { + "0": { + "name": "keyword.operator.ternary.js.jsx" + } + }, + "end": "(:)", + "endCaptures": { + "0": { + "name": "keyword.operator.ternary.js.jsx" + } + }, + "patterns": [ + { + "include": "#expression" + } + ] + }, + "function-call": { + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "patterns": [ + { + "name": "meta.function-call.js.jsx", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "patterns": [ + { + "include": "#literal" + }, + { + "include": "#support-objects" + }, + { + "include": "#object-identifiers" + }, + { + "include": "#punctuation-accessor" + }, + { + "name": "keyword.operator.expression.import.js.jsx", + "match": "(?![\\.\\$])\\bimport(?=\\s*[\\(]\\s*[\\\"\\'\\`])" + }, + { + "name": "entity.name.function.js.jsx", + "match": "([_$[:alpha:]][_$[:alnum:]]*)" + } + ] + }, + { + "include": "#comment" + }, + { + "name": "meta.type.parameters.js.jsx", + "begin": "\\<", + "beginCaptures": { + "0": { + "name": "punctuation.definition.typeparameters.begin.js.jsx" + } + }, + "end": "\\>", + "endCaptures": { + "0": { + "name": "punctuation.definition.typeparameters.end.js.jsx" + } + }, + "patterns": [ + { + "include": "#type" + }, + { + "include": "#punctuation-comma" + } + ] + }, + { + "include": "#paren-expression" + } + ] + }, + "new-expr": { + "name": "new.expr.js.jsx", + "begin": "(?>=|>>>=|\\|=" + }, + { + "name": "keyword.operator.bitwise.shift.js.jsx", + "match": "<<|>>>|>>" + }, + { + "name": "keyword.operator.comparison.js.jsx", + "match": "===|!==|==|!=" + }, + { + "name": "keyword.operator.relational.js.jsx", + "match": "<=|>=|<>|<|>" + }, + { + "name": "keyword.operator.logical.js.jsx", + "match": "\\!|&&|\\|\\|" + }, + { + "name": "keyword.operator.bitwise.js.jsx", + "match": "\\&|~|\\^|\\|" + }, + { + "name": "keyword.operator.assignment.js.jsx", + "match": "\\=" + }, + { + "name": "keyword.operator.decrement.js.jsx", + "match": "--" + }, + { + "name": "keyword.operator.increment.js.jsx", + "match": "\\+\\+" + }, + { + "name": "keyword.operator.arithmetic.js.jsx", + "match": "%|\\*|/|-|\\+" + }, + { + "match": "(?<=[_$[:alnum:])])\\s*(/)(?![/*])", + "captures": { + "1": { + "name": "keyword.operator.arithmetic.js.jsx" + } + } + } + ] + }, + "typeof-operator": { + "name": "keyword.operator.expression.typeof.js.jsx", + "match": "(?=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "captures": { + "1": { + "name": "punctuation.accessor.js.jsx" + }, + "2": { + "name": "support.constant.dom.js.jsx" + }, + "3": { + "name": "support.variable.property.dom.js.jsx" + } + } + }, + { + "name": "support.class.node.js.jsx", + "match": "(?x)(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "captures": { + "1": { + "name": "punctuation.accessor.js.jsx" + }, + "2": { + "name": "entity.name.function.js.jsx" + } + } + }, + { + "match": "(\\.)\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "captures": { + "1": { + "name": "punctuation.accessor.js.jsx" + }, + "2": { + "name": "variable.other.constant.property.js.jsx" + } + } + }, + { + "match": "(\\.)\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "captures": { + "1": { + "name": "punctuation.accessor.js.jsx" + }, + "2": { + "name": "variable.other.property.js.jsx" + } + } + }, + { + "name": "variable.other.constant.js.jsx", + "match": "([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])" + }, + { + "name": "variable.other.readwrite.js.jsx", + "match": "[_$[:alpha:]][_$[:alnum:]]*" + } + ] + }, + "object-identifiers": { + "patterns": [ + { + "name": "support.class.js.jsx", + "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\.\\s*prototype\\b(?!\\$))" + }, + { + "match": "(?x)(\\.)\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "captures": { + "1": { + "name": "punctuation.accessor.js.jsx" + }, + "2": { + "name": "variable.other.constant.object.property.js.jsx" + }, + "3": { + "name": "variable.other.object.property.js.jsx" + } + } + }, + { + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "captures": { + "1": { + "name": "variable.other.constant.object.js.jsx" + }, + "2": { + "name": "variable.other.object.js.jsx" + } + } + } + ] + }, + "type-annotation": { + "patterns": [ + { + "name": "meta.type.annotation.js.jsx", + "begin": "(:)(?=\\s*\\S)", + "beginCaptures": { + "1": { + "name": "keyword.operator.type.annotation.js.jsx" + } + }, + "end": "(?])|((?<=[\\}>\\]\\)]|[_$[:alpha:]])\\s*(?=\\{)))", + "patterns": [ + { + "include": "#type" + } + ] + }, + { + "name": "meta.type.annotation.js.jsx", + "begin": "(:)", + "beginCaptures": { + "1": { + "name": "keyword.operator.type.annotation.js.jsx" + } + }, + "end": "(?])|(?=^\\s*$)|((?<=\\S)(?=\\s*$))|((?<=[\\}>\\]\\)]|[_$[:alpha:]])\\s*(?=\\{)))", + "patterns": [ + { + "include": "#type" + } + ] + } + ] + }, + "return-type": { + "patterns": [ + { + "name": "meta.return.type.js.jsx", + "begin": "(?<=\\))\\s*(:)(?=\\s*\\S)", + "beginCaptures": { + "1": { + "name": "keyword.operator.type.annotation.js.jsx" + } + }, + "end": "(?|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", + "patterns": [ + { + "begin": "(?<=[:])(?=\\s*\\{)", + "end": "(?<=\\})", + "patterns": [ + { + "include": "#type-object" + } + ] + }, + { + "include": "#type-predicate-operator" + }, + { + "include": "#type" + } + ] + }, + "type-parameters": { + "name": "meta.type.parameters.js.jsx", + "begin": "(<)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.typeparameters.begin.js.jsx" + } + }, + "end": "(>)", + "endCaptures": { + "1": { + "name": "punctuation.definition.typeparameters.end.js.jsx" + } + }, + "patterns": [ + { + "include": "#comment" + }, + { + "name": "storage.modifier.js.jsx", + "match": "(?)" + }, + { + "include": "#type" + }, + { + "include": "#punctuation-comma" + } + ] + }, + "type": { + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#string" + }, + { + "include": "#numeric-literal" + }, + { + "include": "#type-primitive" + }, + { + "include": "#type-builtin-literals" + }, + { + "include": "#type-parameters" + }, + { + "include": "#type-tuple" + }, + { + "include": "#type-object" + }, + { + "include": "#type-operators" + }, + { + "include": "#type-fn-type-parameters" + }, + { + "include": "#type-paren-or-function-parameters" + }, + { + "include": "#type-function-return-type" + }, + { + "include": "#type-name" + } + ] + }, + "type-primitive": { + "name": "support.type.primitive.js.jsx", + "match": "(?)\n ))\n )\n )\n)", + "end": "(?<=\\))", + "patterns": [ + { + "include": "#function-parameters" + } + ] + } + ] + }, + "type-function-return-type": { + "patterns": [ + { + "name": "meta.type.function.return.js.jsx", + "begin": "(=>)(?=\\s*\\S)", + "beginCaptures": { + "1": { + "name": "storage.type.function.arrow.js.jsx" + } + }, + "end": "(?)(?]|//|$)", + "patterns": [ + { + "include": "#type-function-return-type-core" + } + ] + }, + { + "name": "meta.type.function.return.js.jsx", + "begin": "=>", + "beginCaptures": { + "0": { + "name": "storage.type.function.arrow.js.jsx" + } + }, + "end": "(?)(?]|//|^\\s*$)|((?<=\\S)(?=\\s*$)))", + "patterns": [ + { + "include": "#type-function-return-type-core" + } + ] + } + ] + }, + "type-function-return-type-core": { + "patterns": [ + { + "include": "#comment" + }, + { + "begin": "(?<==>)(?=\\s*\\{)", + "end": "(?<=\\})", + "patterns": [ + { + "include": "#type-object" + } + ] + }, + { + "include": "#type-predicate-operator" + }, + { + "include": "#type" + } + ] + }, + "type-operators": { + "patterns": [ + { + "include": "#typeof-operator" + }, + { + "begin": "([&|])(?=\\s*\\{)", + "beginCaptures": { + "0": { + "name": "keyword.operator.type.js.jsx" + } + }, + "end": "(?<=\\})", + "patterns": [ + { + "include": "#type-object" + } + ] + }, + { + "begin": "[&|]", + "beginCaptures": { + "0": { + "name": "keyword.operator.type.js.jsx" + } + }, + "end": "(?=\\S)" + }, + { + "name": "keyword.operator.expression.keyof.js.jsx", + "match": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/(?![\\/*])[gimuy]*(?!\\s*[a-zA-Z0-9_$]))", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.js.jsx" + } + }, + "end": "(/)([gimuy]*)", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.js.jsx" + }, + "2": { + "name": "keyword.other.js.jsx" + } + }, + "patterns": [ + { + "include": "#regexp" + } + ] + }, + { + "name": "string.regexp.js.jsx", + "begin": "(?\\s*$)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.js.jsx" + } + }, + "end": "(?=^)", + "patterns": [ + { + "name": "meta.tag.js.jsx", + "begin": "(<)(reference|amd-dependency|amd-module)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.directive.js.jsx" + }, + "2": { + "name": "entity.name.tag.directive.js.jsx" + } + }, + "end": "/>", + "endCaptures": { + "0": { + "name": "punctuation.definition.tag.directive.js.jsx" + } + }, + "patterns": [ + { + "name": "entity.other.attribute-name.directive.js.jsx", + "match": "path|types|no-default-lib|name" + }, + { + "name": "keyword.operator.assignment.js.jsx", + "match": "=" + }, + { + "include": "#string" + } + ] + } + ] + }, + "docblock": { + "patterns": [ + { + "match": "(?x)\n((@)(?:access|api))\n\\s+\n(private|protected|public)\n\\b", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "constant.language.access-type.jsdoc" + } + } + }, + { + "match": "(?x)\n((@)author)\n\\s+\n(\n [^@\\s<>*/]\n (?:[^@<>*/]|\\*[^/])*\n)\n(?:\n \\s*\n (<)\n ([^>\\s]+)\n (>)\n)?", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "entity.name.type.instance.jsdoc" + }, + "4": { + "name": "punctuation.definition.bracket.angle.begin.jsdoc" + }, + "5": { + "name": "constant.other.email.link.underline.jsdoc" + }, + "6": { + "name": "punctuation.definition.bracket.angle.end.jsdoc" + } + } + }, + { + "match": "(?x)\n((@)borrows) \\s+\n((?:[^@\\s*/]|\\*[^/])+) # \n\\s+ (as) \\s+ # as\n((?:[^@\\s*/]|\\*[^/])+) # ", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "entity.name.type.instance.jsdoc" + }, + "4": { + "name": "keyword.operator.control.jsdoc" + }, + "5": { + "name": "entity.name.type.instance.jsdoc" + } + } + }, + { + "name": "meta.example.jsdoc", + "begin": "((@)example)\\s+", + "end": "(?=@|\\*/)", + "beginCaptures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + } + }, + "patterns": [ + { + "match": "^\\s\\*\\s+" + }, + { + "contentName": "constant.other.description.jsdoc", + "begin": "\\G(<)caption(>)", + "beginCaptures": { + "0": { + "name": "entity.name.tag.inline.jsdoc" + }, + "1": { + "name": "punctuation.definition.bracket.angle.begin.jsdoc" + }, + "2": { + "name": "punctuation.definition.bracket.angle.end.jsdoc" + } + }, + "end": "()|(?=\\*/)", + "endCaptures": { + "0": { + "name": "entity.name.tag.inline.jsdoc" + }, + "1": { + "name": "punctuation.definition.bracket.angle.begin.jsdoc" + }, + "2": { + "name": "punctuation.definition.bracket.angle.end.jsdoc" + } + } + }, + { + "match": "[^\\s@*](?:[^*]|\\*[^/])*", + "captures": { + "0": { + "name": "source.embedded.js.jsx" + } + } + } + ] + }, + { + "match": "(?x) ((@)kind) \\s+ (class|constant|event|external|file|function|member|mixin|module|namespace|typedef) \\b", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "constant.language.symbol-type.jsdoc" + } + } + }, + { + "match": "(?x)\n((@)see)\n\\s+\n(?:\n # URL\n (\n (?=https?://)\n (?:[^\\s*]|\\*[^/])+\n )\n |\n # JSDoc namepath\n (\n (?!\n # Avoid matching bare URIs (also acceptable as links)\n https?://\n |\n # Avoid matching {@inline tags}; we match those below\n (?:\\[[^\\[\\]]*\\])? # Possible description [preceding]{@tag}\n {@(?:link|linkcode|linkplain|tutorial)\\b\n )\n # Matched namepath\n (?:[^@\\s*/]|\\*[^/])+\n )\n)", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "variable.other.link.underline.jsdoc" + }, + "4": { + "name": "entity.name.type.instance.jsdoc" + } + } + }, + { + "match": "(?x)\n((@)template)\n\\s+\n# One or more valid identifiers\n(\n [A-Za-z_$] # First character: non-numeric word character\n [\\w$.\\[\\]]* # Rest of identifier\n (?: # Possible list of additional identifiers\n \\s* , \\s*\n [A-Za-z_$]\n [\\w$.\\[\\]]*\n )*\n)", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "variable.other.jsdoc" + } + } + }, + { + "match": "(?x)\n(\n (@)\n (?:arg|argument|const|constant|member|namespace|param|var)\n)\n\\s+\n(\n [A-Za-z_$]\n [\\w$.\\[\\]]*\n)", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "variable.other.jsdoc" + } + } + }, + { + "begin": "((@)typedef)\\s+(?={)", + "beginCaptures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + } + }, + "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", + "patterns": [ + { + "include": "#jsdoctype" + }, + { + "name": "entity.name.type.instance.jsdoc", + "match": "(?:[^@\\s*/]|\\*[^/])+" + } + ] + }, + { + "begin": "((@)(?:arg|argument|const|constant|member|namespace|param|prop|property|var))\\s+(?={)", + "beginCaptures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + } + }, + "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", + "patterns": [ + { + "include": "#jsdoctype" + }, + { + "name": "variable.other.jsdoc", + "match": "([A-Za-z_$][\\w$.\\[\\]]*)" + }, + { + "name": "variable.other.jsdoc", + "match": "(?x)\n(\\[)\\s*\n[\\w$]+\n(?:\n (?:\\[\\])? # Foo[ ].bar properties within an array\n \\. # Foo.Bar namespaced parameter\n [\\w$]+\n)*\n(?:\n \\s*\n (=) # [foo=bar] Default parameter value\n \\s*\n (\n # The inner regexes are to stop the match early at */ and to not stop at escaped quotes\n (?>\n \"(?:(?:\\*(?!/))|(?:\\\\(?!\"))|[^*\\\\])*?\" | # [foo=\"bar\"] Double-quoted\n '(?:(?:\\*(?!/))|(?:\\\\(?!'))|[^*\\\\])*?' | # [foo='bar'] Single-quoted\n \\[ (?:(?:\\*(?!/))|[^*])*? \\] | # [foo=[1,2]] Array literal\n (?:(?:\\*(?!/))|\\s(?!\\s*\\])|\\[.*?(?:\\]|(?=\\*/))|[^*\\s\\[\\]])* # Everything else\n )*\n )\n)?\n\\s*(?:(\\])((?:[^*\\s]|\\*[^\\s/])+)?|(?=\\*/))", + "captures": { + "1": { + "name": "punctuation.definition.optional-value.begin.bracket.square.jsdoc" + }, + "2": { + "name": "keyword.operator.assignment.jsdoc" + }, + "3": { + "name": "source.embedded.js.jsx" + }, + "4": { + "name": "punctuation.definition.optional-value.end.bracket.square.jsdoc" + }, + "5": { + "name": "invalid.illegal.syntax.jsdoc" + } + } + } + ] + }, + { + "begin": "(?x)\n(\n (@)\n (?:define|enum|exception|export|extends|lends|implements|modifies\n |namespace|private|protected|returns?|suppress|this|throws|type\n |yields?)\n)\n\\s+(?={)", + "beginCaptures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + } + }, + "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", + "patterns": [ + { + "include": "#jsdoctype" + } + ] + }, + { + "match": "(?x)\n(\n (@)\n (?:alias|augments|callback|constructs|emits|event|fires|exports?\n |extends|external|function|func|host|lends|listens|interface|memberof!?\n |method|module|mixes|mixin|name|requires|see|this|typedef|uses)\n)\n\\s+\n(\n (?:\n [^{}@\\s*] | \\*[^/]\n )+\n)", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "entity.name.type.instance.jsdoc" + } + } + }, + { + "contentName": "variable.other.jsdoc", + "begin": "((@)(?:default(?:value)?|license|version))\\s+(([''\"]))", + "beginCaptures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "variable.other.jsdoc" + }, + "4": { + "name": "punctuation.definition.string.begin.jsdoc" + } + }, + "end": "(\\3)|(?=$|\\*/)", + "endCaptures": { + "0": { + "name": "variable.other.jsdoc" + }, + "1": { + "name": "punctuation.definition.string.end.jsdoc" + } + } + }, + { + "match": "((@)(?:default(?:value)?|license|tutorial|variation|version))\\s+([^\\s*]+)", + "captures": { + "1": { + "name": "storage.type.class.jsdoc" + }, + "2": { + "name": "punctuation.definition.block.tag.jsdoc" + }, + "3": { + "name": "variable.other.jsdoc" + } + } + }, + { + "name": "storage.type.class.jsdoc", + "match": "(?x) (@) (?:abstract|access|alias|api|arg|argument|async|attribute|augments|author|beta|borrows|bubbles |callback|chainable|class|classdesc|code|config|const|constant|constructor|constructs|copyright |default|defaultvalue|define|deprecated|desc|description|dict|emits|enum|event|example|exception |exports?|extends|extension(?:_?for)?|external|externs|file|fileoverview|final|fires|for|func |function|generator|global|hideconstructor|host|ignore|implements|implicitCast|inherit[Dd]oc |inner|instance|interface|internal|kind|lends|license|listens|main|member|memberof!?|method |mixes|mixins?|modifies|module|name|namespace|noalias|nocollapse|nocompile|nosideeffects |override|overview|package|param|polymer(?:Behavior)?|preserve|private|prop|property|protected |public|read[Oo]nly|record|require[ds]|returns?|see|since|static|struct|submodule|summary |suppress|template|this|throws|todo|tutorial|type|typedef|unrestricted|uses|var|variation |version|virtual|writeOnce|yields?) \\b", + "captures": { + "1": { + "name": "punctuation.definition.block.tag.jsdoc" + } + } + }, + { + "include": "#inline-tags" + } + ] + }, + "brackets": { + "patterns": [ + { + "begin": "{", + "end": "}|(?=\\*/)", + "patterns": [ + { + "include": "#brackets" + } + ] + }, + { + "begin": "\\[", + "end": "\\]|(?=\\*/)", + "patterns": [ + { + "include": "#brackets" + } + ] + } + ] + }, + "inline-tags": { + "patterns": [ + { + "name": "constant.other.description.jsdoc", + "match": "(\\[)[^\\]]+(\\])(?={@(?:link|linkcode|linkplain|tutorial))", + "captures": { + "1": { + "name": "punctuation.definition.bracket.square.begin.jsdoc" + }, + "2": { + "name": "punctuation.definition.bracket.square.end.jsdoc" + } + } + }, + { + "name": "entity.name.type.instance.jsdoc", + "begin": "({)((@)(?:link(?:code|plain)?|tutorial))\\s*", + "beginCaptures": { + "1": { + "name": "punctuation.definition.bracket.curly.begin.jsdoc" + }, + "2": { + "name": "storage.type.class.jsdoc" + }, + "3": { + "name": "punctuation.definition.inline.tag.jsdoc" + } + }, + "end": "}|(?=\\*/)", + "endCaptures": { + "0": { + "name": "punctuation.definition.bracket.curly.end.jsdoc" + } + }, + "patterns": [ + { + "match": "\\G((?=https?://)(?:[^|}\\s*]|\\*[/])+)(\\|)?", + "captures": { + "1": { + "name": "variable.other.link.underline.jsdoc" + }, + "2": { + "name": "punctuation.separator.pipe.jsdoc" + } + } + }, + { + "match": "\\G((?:[^{}@\\s|*]|\\*[^/])+)(\\|)?", + "captures": { + "1": { + "name": "variable.other.description.jsdoc" + }, + "2": { + "name": "punctuation.separator.pipe.jsdoc" + } + } + } + ] + } + ] + }, + "jsdoctype": { + "patterns": [ + { + "name": "invalid.illegal.type.jsdoc", + "match": "\\G{(?:[^}*]|\\*[^/}])+$" + }, + { + "contentName": "entity.name.type.instance.jsdoc", + "begin": "\\G({)", + "beginCaptures": { + "0": { + "name": "entity.name.type.instance.jsdoc" + }, + "1": { + "name": "punctuation.definition.bracket.curly.begin.jsdoc" + } + }, + "end": "((}))\\s*|(?=\\*/)", + "endCaptures": { + "1": { + "name": "entity.name.type.instance.jsdoc" + }, + "2": { + "name": "punctuation.definition.bracket.curly.end.jsdoc" + } + }, + "patterns": [ + { + "include": "#brackets" + } + ] + } + ] + }, + "jsx": { + "patterns": [ + { + "include": "#jsx-tag-without-attributes-in-expression" + }, + { + "include": "#jsx-tag-in-expression" + }, + { + "include": "#jsx-tag-invalid" + } + ] + }, + "jsx-tag-without-attributes-in-expression": { + "begin": "(?x)\n (?<=[({\\[,?=>:*]|&&|\\|\\||\\?|\\Wreturn|^return|\\Wdefault|^)\\s*\n (?=(<)\\s*((?:[a-z][a-z0-9]*|([_$a-zA-Z][-$\\w.]*))(?))", + "end": "(?!\\s*(<)\\s*((?:[a-z][a-z0-9]*|([_$a-zA-Z][-$\\w.]*))(?))", + "patterns": [ + { + "include": "#jsx-tag-without-attributes" + } + ] + }, + "jsx-tag-without-attributes": { + "name": "meta.tag.without-attributes.js.jsx", + "begin": "(<)\\s*((?:[a-z][a-z0-9]*|([_$a-zA-Z][-$\\w.]*))(?)", + "end": "()", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.begin.js.jsx" + }, + "2": { + "name": "entity.name.tag.js.jsx" + }, + "3": { + "name": "support.class.component.js.jsx" + }, + "4": { + "name": "punctuation.definition.tag.end.js.jsx" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.tag.begin.js.jsx" + }, + "2": { + "name": "entity.name.tag.js.jsx" + }, + "3": { + "name": "support.class.component.js.jsx" + }, + "4": { + "name": "punctuation.definition.tag.end.js.jsx" + } + }, + "contentName": "meta.jsx.children.js.jsx", + "patterns": [ + { + "include": "#jsx-children" + } + ] + }, + "jsx-tag-in-expression": { + "begin": "(?x)\n (?<=[({\\[,?=>:*]|&&|\\|\\||\\?|\\Wreturn|^return|\\Wdefault|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*\n ([_$a-zA-Z][-$\\w.]*(?))", + "end": "(/>)|(?:())", + "endCaptures": { + "0": { + "name": "meta.tag.js.jsx" + }, + "1": { + "name": "punctuation.definition.tag.end.js.jsx" + }, + "2": { + "name": "punctuation.definition.tag.begin.js.jsx" + }, + "3": { + "name": "entity.name.tag.js.jsx" + }, + "4": { + "name": "support.class.component.js.jsx" + }, + "5": { + "name": "punctuation.definition.tag.end.js.jsx" + } + }, + "patterns": [ + { + "include": "#jsx-tag" + } + ] + }, + "jsx-child-tag": { + "begin": "(?x)\n (?=(<)\\s*\n ([_$a-zA-Z][-$\\w.]*(?))", + "end": "(/>)|(?:())", + "endCaptures": { + "0": { + "name": "meta.tag.js.jsx" + }, + "1": { + "name": "punctuation.definition.tag.end.js.jsx" + }, + "2": { + "name": "punctuation.definition.tag.begin.js.jsx" + }, + "3": { + "name": "entity.name.tag.js.jsx" + }, + "4": { + "name": "support.class.component.js.jsx" + }, + "5": { + "name": "punctuation.definition.tag.end.js.jsx" + } + }, + "patterns": [ + { + "include": "#jsx-tag" + } + ] + }, + "jsx-tag": { + "name": "meta.tag.js.jsx", + "begin": "(?x)\n (?=(<)\\s*\n ([_$a-zA-Z][-$\\w.]*(?))", + "end": "(?=(/>)|(?:()))", + "patterns": [ + { + "begin": "(?x)\n (<)\\s*\n ((?:[a-z][a-z0-9]*|([_$a-zA-Z][-$\\w.]*))(?)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.begin.js.jsx" + }, + "2": { + "name": "entity.name.tag.js.jsx" + }, + "3": { + "name": "support.class.component.js.jsx" + } + }, + "end": "(?=[/]?>)", + "contentName": "meta.tag.attributes.js.jsx", + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#jsx-tag-attributes" + }, + { + "include": "#jsx-tag-attributes-illegal" + } + ] + }, + { + "begin": "(>)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.end.js.jsx" + } + }, + "end": "(?=" + }, + "jsx-children": { + "patterns": [ + { + "include": "#jsx-tag-without-attributes" + }, + { + "include": "#jsx-child-tag" + }, + { + "include": "#jsx-tag-invalid" + }, + { + "include": "#jsx-evaluated-code" + }, + { + "include": "#jsx-entities" + } + ] + }, + "jsx-evaluated-code": { + "name": "meta.embedded.expression.js.jsx", + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.js.jsx" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.js.jsx" + } + }, + "patterns": [ + { + "include": "#expression" + } + ] + }, + "jsx-entities": { + "patterns": [ + { + "name": "constant.character.entity.js.jsx", + "match": "(&)([a-zA-Z0-9]+|#[0-9]+|#x[0-9a-fA-F]+)(;)", + "captures": { + "1": { + "name": "punctuation.definition.entity.js.jsx" + }, + "3": { + "name": "punctuation.definition.entity.js.jsx" + } + } + }, + { + "name": "invalid.illegal.bad-ampersand.js.jsx", + "match": "&" + } + ] + }, + "jsx-tag-attributes": { + "patterns": [ + { + "include": "#jsx-tag-attribute-name" + }, + { + "include": "#jsx-tag-attribute-assignment" + }, + { + "include": "#jsx-string-double-quoted" + }, + { + "include": "#jsx-string-single-quoted" + }, + { + "include": "#jsx-evaluated-code" + } + ] + }, + "jsx-tag-attribute-name": { + "match": "(?x)\n \\s*\n ([_$a-zA-Z][-$\\w]*)\n (?=\\s|=|/?>|/\\*|//)", + "captures": { + "1": { + "name": "entity.other.attribute-name.js.jsx" + } + } + }, + "jsx-tag-attribute-assignment": { + "name": "keyword.operator.assignment.js.jsx", + "match": "=(?=\\s*(?:'|\"|{|/\\*|//|\\n))" + }, + "jsx-string-double-quoted": { + "name": "string.quoted.double.js.jsx", + "begin": "\"", + "end": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.js.jsx" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.js.jsx" + } + }, + "patterns": [ + { + "include": "#jsx-entities" + } + ] + }, + "jsx-string-single-quoted": { + "name": "string.quoted.single.js.jsx", + "begin": "'", + "end": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.js.jsx" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.js.jsx" + } + }, + "patterns": [ + { + "include": "#jsx-entities" + } + ] + }, + "jsx-tag-attributes-illegal": { + "name": "invalid.illegal.attribute.js.jsx", + "match": "\\S+" + } + } +} \ No newline at end of file diff --git a/extensions/javascript/test/colorize-results/test_jsx.json b/extensions/javascript/test/colorize-results/test_jsx.json index f12ab38729b..7824e567af2 100644 --- a/extensions/javascript/test/colorize-results/test_jsx.json +++ b/extensions/javascript/test/colorize-results/test_jsx.json @@ -1,7 +1,7 @@ [ { "c": "var", - "t": "source.js meta.var.expr.js storage.type.js", + "t": "source.js.jsx meta.var.expr.js.jsx storage.type.js.jsx", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -12,7 +12,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js", + "t": "source.js.jsx meta.var.expr.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -23,7 +23,7 @@ }, { "c": "ToggleText", - "t": "source.js meta.var.expr.js meta.var-single-variable.expr.js meta.definition.variable.js variable.other.readwrite.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.var-single-variable.expr.js.jsx meta.definition.variable.js.jsx variable.other.readwrite.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -34,7 +34,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.var-single-variable.expr.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.var-single-variable.expr.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -45,7 +45,7 @@ }, { "c": "=", - "t": "source.js meta.var.expr.js keyword.operator.assignment.js", + "t": "source.js.jsx meta.var.expr.js.jsx keyword.operator.assignment.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -56,7 +56,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js", + "t": "source.js.jsx meta.var.expr.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -67,7 +67,7 @@ }, { "c": "React", - "t": "source.js meta.var.expr.js meta.function-call.js variable.other.object.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.function-call.js.jsx variable.other.object.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -78,7 +78,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.function-call.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.function-call.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -89,7 +89,7 @@ }, { "c": "createClass", - "t": "source.js meta.var.expr.js meta.function-call.js entity.name.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.function-call.js.jsx entity.name.function.js.jsx", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -100,7 +100,7 @@ }, { "c": "(", - "t": "source.js meta.var.expr.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -111,7 +111,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -122,7 +122,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -133,7 +133,7 @@ }, { "c": "getInitialState", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js entity.name.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx entity.name.function.js.jsx", "r": { "dark_plus": "meta.object-literal.key entity.name.function: #9CDCFE", "light_plus": "meta.object-literal.key entity.name.function: #001080", @@ -144,7 +144,7 @@ }, { "c": ":", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js punctuation.separator.key-value.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx punctuation.separator.key-value.js.jsx", "r": { "dark_plus": "meta.object-literal.key: #9CDCFE", "light_plus": "meta.object-literal.key: #001080", @@ -155,7 +155,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -166,7 +166,7 @@ }, { "c": "function", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js storage.type.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx storage.type.function.js.jsx", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -177,7 +177,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -188,7 +188,7 @@ }, { "c": "(", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.parameters.js.jsx punctuation.definition.parameters.begin.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -199,7 +199,7 @@ }, { "c": ")", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.parameters.js.jsx punctuation.definition.parameters.end.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -210,7 +210,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -221,7 +221,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -232,7 +232,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -243,7 +243,7 @@ }, { "c": "return", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js keyword.control.flow.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx keyword.control.flow.js.jsx", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -254,7 +254,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -265,7 +265,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -276,7 +276,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -287,7 +287,7 @@ }, { "c": "showDefault", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx", "r": { "dark_plus": "meta.object-literal.key: #9CDCFE", "light_plus": "meta.object-literal.key: #001080", @@ -298,7 +298,7 @@ }, { "c": ":", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js punctuation.separator.key-value.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx punctuation.separator.key-value.js.jsx", "r": { "dark_plus": "meta.object-literal.key: #9CDCFE", "light_plus": "meta.object-literal.key: #001080", @@ -309,7 +309,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -320,7 +320,7 @@ }, { "c": "true", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js constant.language.boolean.true.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx constant.language.boolean.true.js.jsx", "r": { "dark_plus": "constant.language: #569CD6", "light_plus": "constant.language: #0000FF", @@ -331,7 +331,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -342,7 +342,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -353,7 +353,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -364,7 +364,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -375,7 +375,7 @@ }, { "c": ",", - "t": "source.js meta.var.expr.js meta.objectliteral.js punctuation.separator.comma.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx punctuation.separator.comma.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -386,7 +386,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -397,7 +397,7 @@ }, { "c": "toggle", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js entity.name.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx entity.name.function.js.jsx", "r": { "dark_plus": "meta.object-literal.key entity.name.function: #9CDCFE", "light_plus": "meta.object-literal.key entity.name.function: #001080", @@ -408,7 +408,7 @@ }, { "c": ":", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js punctuation.separator.key-value.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx punctuation.separator.key-value.js.jsx", "r": { "dark_plus": "meta.object-literal.key: #9CDCFE", "light_plus": "meta.object-literal.key: #001080", @@ -419,7 +419,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -430,7 +430,7 @@ }, { "c": "function", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js storage.type.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx storage.type.function.js.jsx", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -441,7 +441,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -452,7 +452,7 @@ }, { "c": "(", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.parameters.js.jsx punctuation.definition.parameters.begin.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -463,7 +463,7 @@ }, { "c": "e", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.parameters.js variable.parameter.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.parameters.js.jsx variable.parameter.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -474,7 +474,7 @@ }, { "c": ")", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.parameters.js.jsx punctuation.definition.parameters.end.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -485,7 +485,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -496,7 +496,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -507,7 +507,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.whitespace.comment.leading.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.whitespace.comment.leading.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -518,7 +518,7 @@ }, { "c": "//", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.js punctuation.definition.comment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx punctuation.definition.comment.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -529,7 +529,7 @@ }, { "c": " Prevent following the link.", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -540,7 +540,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -551,7 +551,7 @@ }, { "c": "e", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.function-call.js variable.other.object.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.function-call.js.jsx variable.other.object.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -562,7 +562,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.function-call.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.function-call.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -573,7 +573,7 @@ }, { "c": "preventDefault", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.function-call.js support.function.dom.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.function-call.js.jsx support.function.dom.js.jsx", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -584,7 +584,7 @@ }, { "c": "()", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -595,7 +595,7 @@ }, { "c": ";", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.terminator.statement.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.terminator.statement.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -606,7 +606,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.whitespace.comment.leading.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.whitespace.comment.leading.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -617,7 +617,7 @@ }, { "c": "//", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.js punctuation.definition.comment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx punctuation.definition.comment.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -628,7 +628,7 @@ }, { "c": " Invert the chosen default.", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -639,7 +639,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.whitespace.comment.leading.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.whitespace.comment.leading.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -650,7 +650,7 @@ }, { "c": "//", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.js punctuation.definition.comment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx punctuation.definition.comment.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -661,7 +661,7 @@ }, { "c": " This will trigger an intelligent re-render of the component.", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -672,7 +672,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -683,7 +683,7 @@ }, { "c": "this", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.function-call.js variable.language.this.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.function-call.js.jsx variable.language.this.js.jsx", "r": { "dark_plus": "variable.language: #569CD6", "light_plus": "variable.language: #0000FF", @@ -694,7 +694,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.function-call.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.function-call.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -705,7 +705,7 @@ }, { "c": "setState", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.function-call.js entity.name.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.function-call.js.jsx entity.name.function.js.jsx", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -716,7 +716,7 @@ }, { "c": "(", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -727,7 +727,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -738,7 +738,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -749,7 +749,7 @@ }, { "c": "showDefault", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx", "r": { "dark_plus": "meta.object-literal.key: #9CDCFE", "light_plus": "meta.object-literal.key: #001080", @@ -760,7 +760,7 @@ }, { "c": ":", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js punctuation.separator.key-value.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx punctuation.separator.key-value.js.jsx", "r": { "dark_plus": "meta.object-literal.key: #9CDCFE", "light_plus": "meta.object-literal.key: #001080", @@ -771,7 +771,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -782,7 +782,7 @@ }, { "c": "!", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js keyword.operator.logical.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx keyword.operator.logical.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -793,7 +793,7 @@ }, { "c": "this", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js variable.language.this.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx variable.language.this.js.jsx", "r": { "dark_plus": "variable.language: #569CD6", "light_plus": "variable.language: #0000FF", @@ -804,7 +804,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -815,7 +815,7 @@ }, { "c": "state", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js variable.other.object.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx variable.other.object.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -826,7 +826,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -837,7 +837,7 @@ }, { "c": "showDefault", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js variable.other.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx variable.other.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -848,7 +848,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js meta.object.member.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -859,7 +859,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.objectliteral.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.objectliteral.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -870,7 +870,7 @@ }, { "c": ")", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -881,7 +881,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -892,7 +892,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -903,7 +903,7 @@ }, { "c": ",", - "t": "source.js meta.var.expr.js meta.objectliteral.js punctuation.separator.comma.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx punctuation.separator.comma.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -914,7 +914,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -925,7 +925,7 @@ }, { "c": "render", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js entity.name.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx entity.name.function.js.jsx", "r": { "dark_plus": "meta.object-literal.key entity.name.function: #9CDCFE", "light_plus": "meta.object-literal.key entity.name.function: #001080", @@ -936,7 +936,7 @@ }, { "c": ":", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.object-literal.key.js punctuation.separator.key-value.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.object-literal.key.js.jsx punctuation.separator.key-value.js.jsx", "r": { "dark_plus": "meta.object-literal.key: #9CDCFE", "light_plus": "meta.object-literal.key: #001080", @@ -947,7 +947,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -958,7 +958,7 @@ }, { "c": "function", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js storage.type.function.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx storage.type.function.js.jsx", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -969,7 +969,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -980,7 +980,7 @@ }, { "c": "(", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.parameters.js.jsx punctuation.definition.parameters.begin.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -991,7 +991,7 @@ }, { "c": ")", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.parameters.js.jsx punctuation.definition.parameters.end.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1002,7 +1002,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1013,7 +1013,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1024,7 +1024,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.whitespace.comment.leading.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.whitespace.comment.leading.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1035,7 +1035,7 @@ }, { "c": "//", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.js punctuation.definition.comment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx punctuation.definition.comment.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -1046,7 +1046,7 @@ }, { "c": " Default to the default message.", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -1057,7 +1057,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1068,7 +1068,7 @@ }, { "c": "var", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js storage.type.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx storage.type.js.jsx", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -1079,7 +1079,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1090,7 +1090,7 @@ }, { "c": "message", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js meta.var-single-variable.expr.js meta.definition.variable.js variable.other.readwrite.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx meta.var-single-variable.expr.js.jsx meta.definition.variable.js.jsx variable.other.readwrite.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1101,7 +1101,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js meta.var-single-variable.expr.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx meta.var-single-variable.expr.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1112,7 +1112,7 @@ }, { "c": "=", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js keyword.operator.assignment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx keyword.operator.assignment.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1123,7 +1123,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1134,7 +1134,7 @@ }, { "c": "this", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js variable.language.this.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx variable.language.this.js.jsx", "r": { "dark_plus": "variable.language: #569CD6", "light_plus": "variable.language: #0000FF", @@ -1145,7 +1145,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1156,7 +1156,7 @@ }, { "c": "props", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js variable.other.object.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx variable.other.object.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1167,7 +1167,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1178,7 +1178,7 @@ }, { "c": "default", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.var.expr.js variable.other.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.var.expr.js.jsx variable.other.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1189,7 +1189,7 @@ }, { "c": ";", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.terminator.statement.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.terminator.statement.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1200,7 +1200,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.whitespace.comment.leading.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.whitespace.comment.leading.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1211,7 +1211,7 @@ }, { "c": "//", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.js punctuation.definition.comment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx punctuation.definition.comment.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -1222,7 +1222,7 @@ }, { "c": " If toggled, show the alternate message.", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js comment.line.double-slash.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx comment.line.double-slash.js.jsx", "r": { "dark_plus": "comment: #608B4E", "light_plus": "comment: #008000", @@ -1233,7 +1233,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1244,7 +1244,7 @@ }, { "c": "if", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js keyword.control.conditional.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx keyword.control.conditional.js.jsx", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -1255,7 +1255,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1266,7 +1266,7 @@ }, { "c": "(", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1277,7 +1277,7 @@ }, { "c": "!", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js keyword.operator.logical.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx keyword.operator.logical.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1288,7 +1288,7 @@ }, { "c": "this", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js variable.language.this.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx variable.language.this.js.jsx", "r": { "dark_plus": "variable.language: #569CD6", "light_plus": "variable.language: #0000FF", @@ -1299,7 +1299,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1310,7 +1310,7 @@ }, { "c": "state", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js variable.other.object.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx variable.other.object.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1321,7 +1321,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1332,7 +1332,7 @@ }, { "c": "showDefault", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js variable.other.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx variable.other.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1343,7 +1343,7 @@ }, { "c": ")", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1354,7 +1354,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1365,7 +1365,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1376,7 +1376,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1387,7 +1387,7 @@ }, { "c": "message", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js variable.other.readwrite.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx variable.other.readwrite.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1398,7 +1398,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1409,7 +1409,7 @@ }, { "c": "=", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js keyword.operator.assignment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx keyword.operator.assignment.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1420,7 +1420,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1431,7 +1431,7 @@ }, { "c": "this", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js variable.language.this.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx variable.language.this.js.jsx", "r": { "dark_plus": "variable.language: #569CD6", "light_plus": "variable.language: #0000FF", @@ -1442,7 +1442,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1453,7 +1453,7 @@ }, { "c": "props", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js variable.other.object.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx variable.other.object.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1464,7 +1464,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1475,7 +1475,7 @@ }, { "c": "alt", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js support.variable.property.dom.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx support.variable.property.dom.js.jsx", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -1486,7 +1486,7 @@ }, { "c": ";", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js punctuation.terminator.statement.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx punctuation.terminator.statement.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1497,7 +1497,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1508,7 +1508,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1519,7 +1519,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1530,7 +1530,7 @@ }, { "c": "return", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js keyword.control.flow.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx keyword.control.flow.js.jsx", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -1541,7 +1541,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1552,7 +1552,7 @@ }, { "c": "(", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1563,7 +1563,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1574,7 +1574,7 @@ }, { "c": "<", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js punctuation.definition.tag.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx punctuation.definition.tag.begin.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1585,7 +1585,7 @@ }, { "c": "div", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js entity.name.tag.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx entity.name.tag.js.jsx", "r": { "dark_plus": "entity.name.tag: #569CD6", "light_plus": "entity.name.tag: #800000", @@ -1596,7 +1596,7 @@ }, { "c": ">", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js punctuation.definition.tag.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx punctuation.definition.tag.end.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1607,7 +1607,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1618,7 +1618,7 @@ }, { "c": "<", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js punctuation.definition.tag.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx punctuation.definition.tag.begin.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1629,7 +1629,7 @@ }, { "c": "h1", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js entity.name.tag.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx entity.name.tag.js.jsx", "r": { "dark_plus": "entity.name.tag: #569CD6", "light_plus": "entity.name.tag: #800000", @@ -1640,7 +1640,7 @@ }, { "c": ">", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js punctuation.definition.tag.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx punctuation.definition.tag.end.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1651,7 +1651,7 @@ }, { "c": "Hello ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js meta.jsx.children.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1662,7 +1662,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js meta.jsx.children.tsx meta.embedded.expression.js punctuation.section.embedded.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.embedded.expression.js.jsx punctuation.section.embedded.begin.js.jsx", "r": { "dark_plus": "punctuation.section.embedded: #569CD6", "light_plus": "punctuation.section.embedded: #0000FF", @@ -1673,7 +1673,7 @@ }, { "c": "message", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js meta.jsx.children.tsx meta.embedded.expression.js variable.other.readwrite.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.embedded.expression.js.jsx variable.other.readwrite.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1684,7 +1684,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js meta.jsx.children.tsx meta.embedded.expression.js punctuation.section.embedded.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.embedded.expression.js.jsx punctuation.section.embedded.end.js.jsx", "r": { "dark_plus": "punctuation.section.embedded: #569CD6", "light_plus": "punctuation.section.embedded: #0000FF", @@ -1695,7 +1695,7 @@ }, { "c": "!", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js meta.jsx.children.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1706,7 +1706,7 @@ }, { "c": "", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.without-attributes.js punctuation.definition.tag.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.without-attributes.js.jsx punctuation.definition.tag.end.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1739,7 +1739,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1750,7 +1750,7 @@ }, { "c": "<", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js punctuation.definition.tag.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx punctuation.definition.tag.begin.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1761,7 +1761,7 @@ }, { "c": "a", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js entity.name.tag.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx entity.name.tag.js.jsx", "r": { "dark_plus": "entity.name.tag: #569CD6", "light_plus": "entity.name.tag: #800000", @@ -1772,7 +1772,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1783,7 +1783,7 @@ }, { "c": "href", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js entity.other.attribute-name.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx entity.other.attribute-name.js.jsx", "r": { "dark_plus": "entity.other.attribute-name: #9CDCFE", "light_plus": "entity.other.attribute-name: #FF0000", @@ -1794,7 +1794,7 @@ }, { "c": "=", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js keyword.operator.assignment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx keyword.operator.assignment.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1805,7 +1805,7 @@ }, { "c": "\"", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js string.quoted.double.js punctuation.definition.string.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx punctuation.definition.string.begin.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1816,7 +1816,7 @@ }, { "c": "\"", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js string.quoted.double.js punctuation.definition.string.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx punctuation.definition.string.end.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1827,7 +1827,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1838,7 +1838,7 @@ }, { "c": "onClick", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js entity.other.attribute-name.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx entity.other.attribute-name.js.jsx", "r": { "dark_plus": "entity.other.attribute-name: #9CDCFE", "light_plus": "entity.other.attribute-name: #FF0000", @@ -1849,7 +1849,7 @@ }, { "c": "=", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js keyword.operator.assignment.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx keyword.operator.assignment.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1860,7 +1860,7 @@ }, { "c": "{", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js meta.embedded.expression.js punctuation.section.embedded.begin.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx meta.embedded.expression.js.jsx punctuation.section.embedded.begin.js.jsx", "r": { "dark_plus": "punctuation.section.embedded: #569CD6", "light_plus": "punctuation.section.embedded: #0000FF", @@ -1871,7 +1871,7 @@ }, { "c": "this", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js meta.embedded.expression.js variable.language.this.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx meta.embedded.expression.js.jsx variable.language.this.js.jsx", "r": { "dark_plus": "variable.language: #569CD6", "light_plus": "variable.language: #0000FF", @@ -1882,7 +1882,7 @@ }, { "c": ".", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js meta.embedded.expression.js punctuation.accessor.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx meta.embedded.expression.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "meta.embedded: #D4D4D4", "light_plus": "meta.embedded: #000000", @@ -1893,7 +1893,7 @@ }, { "c": "toggle", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js meta.embedded.expression.js variable.other.property.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx meta.embedded.expression.js.jsx variable.other.property.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1904,7 +1904,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js meta.embedded.expression.js punctuation.section.embedded.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx meta.embedded.expression.js.jsx punctuation.section.embedded.end.js.jsx", "r": { "dark_plus": "punctuation.section.embedded: #569CD6", "light_plus": "punctuation.section.embedded: #0000FF", @@ -1915,7 +1915,7 @@ }, { "c": ">", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js punctuation.definition.tag.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx punctuation.definition.tag.end.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1926,7 +1926,7 @@ }, { "c": "Toggle", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js meta.jsx.children.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx meta.jsx.children.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1937,7 +1937,7 @@ }, { "c": "", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx meta.tag.js punctuation.definition.tag.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx meta.tag.js.jsx punctuation.definition.tag.end.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1970,7 +1970,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js meta.jsx.children.tsx", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx meta.jsx.children.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1981,7 +1981,7 @@ }, { "c": "", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.tag.without-attributes.js punctuation.definition.tag.end.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.tag.without-attributes.js.jsx punctuation.definition.tag.end.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -2014,7 +2014,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2025,7 +2025,7 @@ }, { "c": ")", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2036,7 +2036,7 @@ }, { "c": ";", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.terminator.statement.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.terminator.statement.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2047,7 +2047,7 @@ }, { "c": " ", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2058,7 +2058,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js meta.object.member.js meta.function.expression.js meta.block.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2069,7 +2069,7 @@ }, { "c": "}", - "t": "source.js meta.var.expr.js meta.objectliteral.js punctuation.definition.block.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx punctuation.definition.block.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2080,7 +2080,7 @@ }, { "c": ")", - "t": "source.js meta.var.expr.js meta.brace.round.js", + "t": "source.js.jsx meta.var.expr.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2091,7 +2091,7 @@ }, { "c": ";", - "t": "source.js punctuation.terminator.statement.js", + "t": "source.js.jsx punctuation.terminator.statement.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2102,7 +2102,7 @@ }, { "c": "React", - "t": "source.js meta.function-call.js variable.other.object.js", + "t": "source.js.jsx meta.function-call.js.jsx variable.other.object.js.jsx", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -2113,7 +2113,7 @@ }, { "c": ".", - "t": "source.js meta.function-call.js punctuation.accessor.js", + "t": "source.js.jsx meta.function-call.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2124,7 +2124,7 @@ }, { "c": "render", - "t": "source.js meta.function-call.js entity.name.function.js", + "t": "source.js.jsx meta.function-call.js.jsx entity.name.function.js.jsx", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -2135,7 +2135,7 @@ }, { "c": "(", - "t": "source.js meta.brace.round.js", + "t": "source.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2146,7 +2146,7 @@ }, { "c": "<", - "t": "source.js meta.tag.js punctuation.definition.tag.begin.js", + "t": "source.js.jsx meta.tag.js.jsx punctuation.definition.tag.begin.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -2157,7 +2157,7 @@ }, { "c": "ToggleText", - "t": "source.js meta.tag.js entity.name.tag.js support.class.component.js", + "t": "source.js.jsx meta.tag.js.jsx entity.name.tag.js.jsx support.class.component.js.jsx", "r": { "dark_plus": "support.class: #4EC9B0", "light_plus": "support.class: #267F99", @@ -2168,7 +2168,7 @@ }, { "c": " ", - "t": "source.js meta.tag.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2179,7 +2179,7 @@ }, { "c": "default", - "t": "source.js meta.tag.js entity.other.attribute-name.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx entity.other.attribute-name.js.jsx", "r": { "dark_plus": "entity.other.attribute-name: #9CDCFE", "light_plus": "entity.other.attribute-name: #FF0000", @@ -2190,7 +2190,7 @@ }, { "c": "=", - "t": "source.js meta.tag.js keyword.operator.assignment.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx keyword.operator.assignment.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -2201,7 +2201,7 @@ }, { "c": "\"", - "t": "source.js meta.tag.js string.quoted.double.js punctuation.definition.string.begin.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx punctuation.definition.string.begin.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2212,7 +2212,7 @@ }, { "c": "World", - "t": "source.js meta.tag.js string.quoted.double.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2223,7 +2223,7 @@ }, { "c": "\"", - "t": "source.js meta.tag.js string.quoted.double.js punctuation.definition.string.end.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx punctuation.definition.string.end.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2234,7 +2234,7 @@ }, { "c": " ", - "t": "source.js meta.tag.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2245,7 +2245,7 @@ }, { "c": "alt", - "t": "source.js meta.tag.js entity.other.attribute-name.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx entity.other.attribute-name.js.jsx", "r": { "dark_plus": "entity.other.attribute-name: #9CDCFE", "light_plus": "entity.other.attribute-name: #FF0000", @@ -2256,7 +2256,7 @@ }, { "c": "=", - "t": "source.js meta.tag.js keyword.operator.assignment.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx keyword.operator.assignment.js.jsx", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -2267,7 +2267,7 @@ }, { "c": "\"", - "t": "source.js meta.tag.js string.quoted.double.js punctuation.definition.string.begin.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx punctuation.definition.string.begin.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2278,7 +2278,7 @@ }, { "c": "Mars", - "t": "source.js meta.tag.js string.quoted.double.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2289,7 +2289,7 @@ }, { "c": "\"", - "t": "source.js meta.tag.js string.quoted.double.js punctuation.definition.string.end.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx string.quoted.double.js.jsx punctuation.definition.string.end.js.jsx", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2300,7 +2300,7 @@ }, { "c": " ", - "t": "source.js meta.tag.js", + "t": "source.js.jsx meta.tag.js.jsx meta.tag.attributes.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2311,7 +2311,7 @@ }, { "c": "/>", - "t": "source.js meta.tag.js punctuation.definition.tag.end.js", + "t": "source.js.jsx meta.tag.js.jsx punctuation.definition.tag.end.js.jsx", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -2322,7 +2322,7 @@ }, { "c": ",", - "t": "source.js punctuation.separator.comma.js", + "t": "source.js.jsx punctuation.separator.comma.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2333,7 +2333,7 @@ }, { "c": " ", - "t": "source.js", + "t": "source.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2344,7 +2344,7 @@ }, { "c": "document", - "t": "source.js support.variable.dom.js", + "t": "source.js.jsx support.variable.dom.js.jsx", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -2355,7 +2355,7 @@ }, { "c": ".", - "t": "source.js punctuation.accessor.js", + "t": "source.js.jsx punctuation.accessor.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2366,7 +2366,7 @@ }, { "c": "body", - "t": "source.js support.variable.property.dom.js", + "t": "source.js.jsx support.variable.property.dom.js.jsx", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -2377,7 +2377,7 @@ }, { "c": ")", - "t": "source.js meta.brace.round.js", + "t": "source.js.jsx meta.brace.round.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2388,7 +2388,7 @@ }, { "c": ";", - "t": "source.js punctuation.terminator.statement.js", + "t": "source.js.jsx punctuation.terminator.statement.js.jsx", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/json/client/src/jsonMain.ts b/extensions/json/client/src/jsonMain.ts index 1498820c33d..2509a46e385 100644 --- a/extensions/json/client/src/jsonMain.ts +++ b/extensions/json/client/src/jsonMain.ts @@ -145,10 +145,10 @@ export function activate(context: ExtensionContext) { }); }); }, - provideColorPresentations(document: TextDocument, colorInfo: ColorInformation): Thenable { + provideColorPresentations(color: Color, context): Thenable { let params: ColorPresentationParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - colorInfo: { range: client.code2ProtocolConverter.asRange(colorInfo.range), color: colorInfo.color } + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(context.document), + colorInfo: { range: client.code2ProtocolConverter.asRange(context.range), color } }; return client.sendRequest(ColorPresentationRequest.type, params).then(presentations => { return presentations.map(p => { @@ -288,4 +288,4 @@ function getPackageInfo(context: ExtensionContext): IPackageInfo { }; } return null; -} \ No newline at end of file +} diff --git a/extensions/json/npm-shrinkwrap.json b/extensions/json/npm-shrinkwrap.json index 2b10b555249..edf04fe0a48 100644 --- a/extensions/json/npm-shrinkwrap.json +++ b/extensions/json/npm-shrinkwrap.json @@ -13,24 +13,24 @@ "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz" }, "vscode-jsonrpc": { - "version": "3.4.0", - "from": "vscode-jsonrpc@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-jsonrpc@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0-next.1.tgz" }, "vscode-languageclient": { - "version": "3.4.2", + "version": "3.5.0-next.3", "from": "vscode-languageclient@next", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.4.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.5.0-next.3.tgz" }, "vscode-languageserver-protocol": { - "version": "3.4.2", - "from": "vscode-languageserver-protocol@3.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.2.tgz" + "version": "3.5.0-next.3", + "from": "vscode-languageserver-protocol@>=3.5.0-next.3 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0-next.3.tgz" }, "vscode-languageserver-types": { - "version": "3.4.0", - "from": "vscode-languageserver-types@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-languageserver-types@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0-next.1.tgz" }, "vscode-nls": { "version": "2.0.2", @@ -43,4 +43,4 @@ "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.3.tgz" } } -} \ No newline at end of file +} diff --git a/extensions/json/package.json b/extensions/json/package.json index c27ae547702..b85adbd6fad 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -133,8 +133,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.8", - "vscode-languageclient": "^3.4.2", - "vscode-languageserver-protocol": "^3.4.2", + "vscode-languageclient": "^3.5.0-next.3", "vscode-nls": "2.0.2" }, "devDependencies": { diff --git a/extensions/json/server/npm-shrinkwrap.json b/extensions/json/server/npm-shrinkwrap.json index 31c46482d9a..4f69e09581f 100644 --- a/extensions/json/server/npm-shrinkwrap.json +++ b/extensions/json/server/npm-shrinkwrap.json @@ -43,24 +43,31 @@ "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.2.1.tgz" }, "vscode-json-languageservice": { - "version": "2.0.20", + "version": "2.0.22", "from": "vscode-json-languageservice@next", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.20.tgz" + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.22.tgz" }, "vscode-jsonrpc": { - "version": "3.4.0", - "from": "vscode-jsonrpc@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz" + "version": "3.5.0-next.1", + "from": "vscode-jsonrpc@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0-next.1.tgz" }, "vscode-languageserver": { - "version": "3.4.2", + "version": "3.5.0-next.2", "from": "vscode-languageserver@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.4.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.0-next.2.tgz" }, "vscode-languageserver-protocol": { - "version": "3.4.2", - "from": "vscode-languageserver-protocol@3.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.2.tgz" + "version": "3.5.0-next.3", + "from": "vscode-languageserver-protocol@>=3.5.0-next.2 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0-next.3.tgz", + "dependencies": { + "vscode-languageserver-types": { + "version": "3.5.0-next.1", + "from": "vscode-languageserver-types@>=3.5.0-next.1 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0-next.1.tgz" + } + } }, "vscode-languageserver-types": { "version": "3.4.0", diff --git a/extensions/json/server/package.json b/extensions/json/server/package.json index 8c9a7a3f113..e570819c565 100644 --- a/extensions/json/server/package.json +++ b/extensions/json/server/package.json @@ -10,10 +10,8 @@ "dependencies": { "jsonc-parser": "^1.0.0", "request-light": "^0.2.1", - "vscode-json-languageservice": "^2.0.20", - "vscode-languageserver": "^3.4.2", - "vscode-languageserver-protocol": "^3.4.2", - "vscode-languageserver-types": "^3.4.0", + "vscode-json-languageservice": "^2.0.22", + "vscode-languageserver": "^3.5.0-next.2", "vscode-nls": "^2.0.2", "vscode-uri": "^1.0.1" }, diff --git a/extensions/markdown/package.json b/extensions/markdown/package.json index 769c6091855..5cb34c4c662 100644 --- a/extensions/markdown/package.json +++ b/extensions/markdown/package.json @@ -46,6 +46,7 @@ "meta.embedded.block.html": "html", "source.js": "javascript", "source.css": "css", + "meta.embedded.block.frontmatter": "yaml", "meta.embedded.block.css": "css", "meta.embedded.block.ini": "ini", diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage b/extensions/markdown/syntaxes/markdown.tmLanguage index 7d5448a80d2..5e7d26dea2b 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage +++ b/extensions/markdown/syntaxes/markdown.tmLanguage @@ -3691,6 +3691,8 @@ frontMatter + contentName + meta.embedded.block.frontmatter begin \A-{3}\s*$ while diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage.base b/extensions/markdown/syntaxes/markdown.tmLanguage.base index 6d0a8510217..d501f50a86f 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage.base +++ b/extensions/markdown/syntaxes/markdown.tmLanguage.base @@ -1181,6 +1181,8 @@ frontMatter + contentName + meta.embedded.block.frontmatter begin \A-{3}\s*$ while diff --git a/extensions/npm-shrinkwrap.json b/extensions/npm-shrinkwrap.json index 4a636d0dcfe..6241de07cd9 100644 --- a/extensions/npm-shrinkwrap.json +++ b/extensions/npm-shrinkwrap.json @@ -3,9 +3,9 @@ "version": "0.0.1", "dependencies": { "typescript": { - "version": "2.5.3", - "from": "typescript@2.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz" + "version": "2.6.1-insiders.20171016", + "from": "typescript@2.6.1-insiders.20171016", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.1-insiders.20171016.tgz" } } } diff --git a/extensions/package.json b/extensions/package.json index 798babbda78..bcf3e8b0dc8 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "2.5.3" + "typescript": "2.6.1-insiders.20171016" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/powershell/language-configuration.json b/extensions/powershell/language-configuration.json index 1227fef697d..b03aa5cd42e 100644 --- a/extensions/powershell/language-configuration.json +++ b/extensions/powershell/language-configuration.json @@ -25,8 +25,8 @@ ], "folding": { "markers": { - "start": "^\\s*#region", - "end": "^\\s*#endregion" + "start": "^\\s*#region\\b", + "end": "^\\s*#endregion\\b" } } } \ No newline at end of file diff --git a/extensions/python/language-configuration.json b/extensions/python/language-configuration.json index 51892242cc4..14ad98220b4 100644 --- a/extensions/python/language-configuration.json +++ b/extensions/python/language-configuration.json @@ -25,8 +25,8 @@ "folding": { "offSide": true, "markers": { - "start": "^\\s*#region", - "end": "^\\s*#endregion" + "start": "^\\s*#region\\b", + "end": "^\\s*#endregion\\b" } } } diff --git a/extensions/typescript/build/update-grammars.js b/extensions/typescript/build/update-grammars.js index c38d6f10510..bae3329d9b1 100644 --- a/extensions/typescript/build/update-grammars.js +++ b/extensions/typescript/build/update-grammars.js @@ -6,14 +6,17 @@ var updateGrammar = require('../../../build/npm/update-grammar'); -function adaptToJavaScript(grammar) { +function adaptToJavaScript(grammar, replacementScope) { grammar.name = 'JavaScript (with React support)'; grammar.fileTypes = ['.js', '.jsx', '.es6', '.mjs' ]; - grammar.scopeName = 'source.js'; + grammar.scopeName = `source${replacementScope}`; var fixScopeNames = function(rule) { if (typeof rule.name === 'string') { - rule.name = rule.name.replace(/\.tsx/g, '.js'); + rule.name = rule.name.replace(/\.tsx/g, replacementScope); + } + if (typeof rule.contentName === 'string') { + rule.contentName = rule.contentName.replace(/\.tsx/g, replacementScope); } for (var property in rule) { var value = rule[property]; @@ -32,7 +35,9 @@ function adaptToJavaScript(grammar) { var tsGrammarRepo = 'Microsoft/TypeScript-TmLanguage'; updateGrammar.update(tsGrammarRepo, 'TypeScript.tmLanguage', './syntaxes/TypeScript.tmLanguage.json'); updateGrammar.update(tsGrammarRepo, 'TypeScriptReact.tmLanguage', './syntaxes/TypeScriptReact.tmLanguage.json'); -updateGrammar.update(tsGrammarRepo, 'TypeScriptReact.tmLanguage', '../javascript/syntaxes/JavaScript.tmLanguage.json', adaptToJavaScript); +updateGrammar.update(tsGrammarRepo, 'TypeScriptReact.tmLanguage', '../javascript/syntaxes/JavaScript.tmLanguage.json', grammar => adaptToJavaScript(grammar, '.js')); +updateGrammar.update(tsGrammarRepo, 'TypeScriptReact.tmLanguage', '../javascript/syntaxes/JavaScriptReact.tmLanguage.json', grammar => adaptToJavaScript(grammar, '.js.jsx')); + diff --git a/extensions/typescript/language-configuration.json b/extensions/typescript/language-configuration.json index ad48834d04a..1e8f440a420 100644 --- a/extensions/typescript/language-configuration.json +++ b/extensions/typescript/language-configuration.json @@ -27,8 +27,8 @@ ], "folding": { "markers": { - "start": "^\\s*//\\s*#?region", - "end": "^\\s*//\\s*#?endregion" + "start": "^\\s*//\\s*#?region\\b", + "end": "^\\s*//\\s*#?endregion\\b" } } } \ No newline at end of file diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 11b93b77e10..3062949aad7 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -78,6 +78,7 @@ "embeddedLanguages": { "meta.tag.tsx": "jsx-tags", "meta.tag.without-attributes.tsx": "jsx-tags", + "meta.tag.attributes.tsx": "typescriptreact", "meta.embedded.expression.tsx": "typescriptreact" } } @@ -386,10 +387,18 @@ "default": "on", "enum": [ "on", - "off" + "off", + "build", + "watch" ], "description": "%typescript.tsc.autoDetect%", "scope": "resource" + }, + "typescript.quickSuggestionsForPaths": { + "type": "boolean", + "default": true, + "description": "%typescript.quickSuggestionsForPaths%", + "scope": "resource" } } }, @@ -568,4 +577,4 @@ } ] } -} +} \ No newline at end of file diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index b25953666f6..39725205f50 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -39,7 +39,8 @@ "typescript.npm": "Specifies the path to the NPM executable used for Automatic Type Acquisition. Requires TypeScript >= 2.3.4.", "typescript.check.npmIsInstalled": "Check if NPM is installed for Automatic Type Acquisition.", "javascript.nameSuggestions": "Enable/disable including unique names from the file in JavaScript suggestion lists.", - "typescript.tsc.autoDetect": "Controls whether auto detection of tsc tasks is on or off.", + "typescript.tsc.autoDetect": "Controls auto detection of tsc tasks. 'off' disables this feature. 'build' only creates single run compile tasks. 'watch' only creates compile and watch tasks. 'on' creates both build and watch tasks. Default is 'on'.", "typescript.problemMatchers.tsc.label": "TypeScript problems", - "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)" + "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)", + "typescript.quickSuggestionsForPaths": "Enable/disable quick suggestions when typing out an import path." } diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 1d7b35cd4e2..f4e46e19cee 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -122,34 +122,36 @@ class MyCompletionItem extends CompletionItem { interface Configuration { useCodeSnippetsOnMethodSuggest: boolean; nameSuggestions: boolean; + quickSuggestionsForPaths: boolean; } namespace Configuration { export const useCodeSnippetsOnMethodSuggest = 'useCodeSnippetsOnMethodSuggest'; export const nameSuggestions = 'nameSuggestions'; + export const quickSuggestionsForPaths = 'quickSuggestionsForPaths'; } export default class TypeScriptCompletionItemProvider implements CompletionItemProvider { - private config: Configuration; + private config: Configuration = { + useCodeSnippetsOnMethodSuggest: false, + nameSuggestions: true, + quickSuggestionsForPaths: true + }; constructor( private client: ITypescriptServiceClient, private typingsStatus: TypingsStatus - ) { - this.config = { - useCodeSnippetsOnMethodSuggest: false, - nameSuggestions: true - }; - } + ) { } public updateConfiguration(): void { // Use shared setting for js and ts const typeScriptConfig = workspace.getConfiguration('typescript'); - this.config.useCodeSnippetsOnMethodSuggest = typeScriptConfig.get(Configuration.useCodeSnippetsOnMethodSuggest, false); - const jsConfig = workspace.getConfiguration('javascript'); - this.config.nameSuggestions = jsConfig.get(Configuration.nameSuggestions, true); + this.config.useCodeSnippetsOnMethodSuggest = typeScriptConfig.get(Configuration.useCodeSnippetsOnMethodSuggest, false); + this.config.quickSuggestionsForPaths = typeScriptConfig.get(Configuration.quickSuggestionsForPaths, true); + + this.config.nameSuggestions = workspace.getConfiguration('javascript').get(Configuration.nameSuggestions, true); } public provideCompletionItems( @@ -175,14 +177,22 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP } if (context.triggerCharacter === '"' || context.triggerCharacter === '\'') { + if (!this.config.quickSuggestionsForPaths) { + return Promise.resolve([]); + } + // make sure we are in something that looks like the start of an import const line = document.lineAt(position.line).text.slice(0, position.character); - if (!line.match(/\bfrom\s*["']$/) && !line.match(/\b(import|require)\(['"]$/)) { + if (!line.match(/\b(from|import)\s*["']$/) && !line.match(/\b(import|require)\(['"]$/)) { return Promise.resolve([]); } } if (context.triggerCharacter === '/') { + if (!this.config.quickSuggestionsForPaths) { + return Promise.resolve([]); + } + // make sure we are in something that looks like an import path const line = document.lineAt(position.line).text.slice(0, position.character); if (!line.match(/\bfrom\s*["'][^'"]*$/) && !line.match(/\b(import|require)\(['"][^'"]*$/)) { diff --git a/extensions/typescript/src/features/definitionProviderBase.ts b/extensions/typescript/src/features/definitionProviderBase.ts index bf5ffabd704..ee3a086a4fa 100644 --- a/extensions/typescript/src/features/definitionProviderBase.ts +++ b/extensions/typescript/src/features/definitionProviderBase.ts @@ -13,7 +13,7 @@ export default class TypeScriptDefinitionProviderBase { constructor( private client: ITypescriptServiceClient) { } - protected getSymbolLocations( + protected async getSymbolLocations( definitionType: 'definition' | 'implementation' | 'typeDefinition', document: TextDocument, position: Position, @@ -21,24 +21,24 @@ export default class TypeScriptDefinitionProviderBase { ): Promise { const filepath = this.client.normalizePath(document.uri); if (!filepath) { - return Promise.resolve(null); + return null; } + const args = vsPositionToTsFileLocation(filepath, position); - return this.client.execute(definitionType, args, token).then(response => { + try { + const response = await this.client.execute(definitionType, args, token); const locations: Proto.FileSpan[] = (response && response.body) || []; if (!locations || locations.length === 0) { return []; } return locations.map(location => { const resource = this.client.asUrl(location.file); - if (resource === null) { - return null; - } else { - return new Location(resource, tsTextSpanToVsRange(location)); - } - }).filter(x => x !== null) as Location[]; - }, () => { + return !resource + ? null + : new Location(resource, tsTextSpanToVsRange(location)); + }).filter(x => x) as Location[]; + } catch { return []; - }); + } } } \ No newline at end of file diff --git a/extensions/typescript/src/features/documentHighlightProvider.ts b/extensions/typescript/src/features/documentHighlightProvider.ts index d91cb8fd90b..871629630dc 100644 --- a/extensions/typescript/src/features/documentHighlightProvider.ts +++ b/extensions/typescript/src/features/documentHighlightProvider.ts @@ -13,14 +13,20 @@ export default class TypeScriptDocumentHighlightProvider implements DocumentHigh public constructor( private client: ITypescriptServiceClient) { } - public provideDocumentHighlights(resource: TextDocument, position: Position, token: CancellationToken): Promise { + public async provideDocumentHighlights( + resource: TextDocument, + position: Position, + token: CancellationToken + ): Promise { const filepath = this.client.normalizePath(resource.uri); if (!filepath) { - return Promise.resolve([]); + return []; } + const args = vsPositionToTsFileLocation(filepath, position); - return this.client.execute('occurrences', args, token).then((response): DocumentHighlight[] => { - let data = response.body; + try { + const response = await this.client.execute('occurrences', args, token); + const data = response.body; if (data && data.length) { // Workaround for https://github.com/Microsoft/TypeScript/issues/12780 // Don't highlight string occurrences @@ -39,8 +45,8 @@ export default class TypeScriptDocumentHighlightProvider implements DocumentHigh item.isWriteAccess ? DocumentHighlightKind.Write : DocumentHighlightKind.Read)); } return []; - }, () => { + } catch { return []; - }); + } } } \ No newline at end of file diff --git a/extensions/typescript/src/features/taskProvider.ts b/extensions/typescript/src/features/taskProvider.ts index 46dce9fa311..d7d042e9abb 100644 --- a/extensions/typescript/src/features/taskProvider.ts +++ b/extensions/typescript/src/features/taskProvider.ts @@ -17,6 +17,8 @@ import { isImplicitProjectConfigFile } from '../utils/tsconfig'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); +type AutoDetect = 'on' | 'off' | 'build' | 'watch'; + const exists = (file: string): Promise => new Promise((resolve, _reject) => { @@ -35,12 +37,21 @@ interface TypeScriptTaskDefinition extends vscode.TaskDefinition { * Provides tasks for building `tsconfig.json` files in a project. */ class TscTaskProvider implements vscode.TaskProvider { + private autoDetect: AutoDetect = 'on'; private readonly tsconfigProvider: TsConfigProvider; + private readonly disposables: vscode.Disposable[] = []; public constructor( private readonly lazyClient: () => TypeScriptServiceClient ) { this.tsconfigProvider = new TsConfigProvider(); + + vscode.workspace.onDidChangeConfiguration(this.onConfigurationChanged, this, this.disposables); + this.onConfigurationChanged(); + } + + dispose() { + this.disposables.forEach(x => x.dispose()); } public async provideTasks(token: vscode.CancellationToken): Promise { @@ -149,8 +160,42 @@ class TscTaskProvider implements vscode.TaskProvider { private async getTasksForProject(project: TSConfig): Promise { const command = await this.getCommand(project); + const label = this.getLabelForTasks(project); - let label: string = project.path; + const tasks: vscode.Task[] = []; + + if (this.autoDetect === 'build' || this.autoDetect === 'on') { + const buildTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label }; + const buildTask = new vscode.Task( + buildTaskidentifier, + project.workspaceFolder || vscode.TaskScope.Workspace, + localize('buildTscLabel', 'build - {0}', label), + 'tsc', + new vscode.ShellExecution(`${command} -p "${project.path}"`), + '$tsc'); + buildTask.group = vscode.TaskGroup.Build; + buildTask.isBackground = false; + tasks.push(buildTask); + } + + if (this.autoDetect === 'watch' || this.autoDetect === 'on') { + const watchTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label, option: 'watch' }; + const watchTask = new vscode.Task( + watchTaskidentifier, + project.workspaceFolder || vscode.TaskScope.Workspace, + localize('buildAndWatchTscLabel', 'watch - {0}', label), + 'tsc', + new vscode.ShellExecution(`${command} --watch -p "${project.path}"`), + '$tsc-watch'); + watchTask.group = vscode.TaskGroup.Build; + watchTask.isBackground = true; + tasks.push(watchTask); + } + + return tasks; + } + + private getLabelForTasks(project: TSConfig): string { if (project.workspaceFolder) { const projectFolder = project.workspaceFolder; const workspaceFolders = vscode.workspace.workspaceFolders; @@ -158,41 +203,23 @@ class TscTaskProvider implements vscode.TaskProvider { if (workspaceFolders && workspaceFolders.length > 1) { // Use absolute path when we have multiple folders with the same name if (workspaceFolders.filter(x => x.name === projectFolder.name).length > 1) { - label = path.join(project.workspaceFolder.uri.fsPath, relativePath); + return path.join(project.workspaceFolder.uri.fsPath, relativePath); } else { - label = path.join(project.workspaceFolder.name, relativePath); + return path.join(project.workspaceFolder.name, relativePath); } } else { - label = relativePath; + return relativePath; } } + return project.path; + } - const buildTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label }; - const buildTask = new vscode.Task( - buildTaskidentifier, - localize('buildTscLabel', 'build - {0}', label), - 'tsc', - new vscode.ShellExecution(`${command} -p "${project.path}"`), - '$tsc'); - buildTask.group = vscode.TaskGroup.Build; - buildTask.isBackground = false; - - const watchTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label, option: 'watch' }; - const watchTask = new vscode.Task( - watchTaskidentifier, - localize('buildAndWatchTscLabel', 'watch - {0}', label), - 'tsc', - new vscode.ShellExecution(`${command} --watch -p "${project.path}"`), - '$tsc-watch'); - watchTask.group = vscode.TaskGroup.Build; - watchTask.isBackground = true; - - return [buildTask, watchTask]; + private onConfigurationChanged(): void { + const type = vscode.workspace.getConfiguration('typescript.tsc').get('autoDetect'); + this.autoDetect = typeof type === 'undefined' ? 'on' : type; } } -type AutoDetect = 'on' | 'off'; - /** * Manages registrations of TypeScript task provides with VScode. */ @@ -216,11 +243,11 @@ export default class TypeScriptTaskProviderManager { } private onConfigurationChanged() { - let autoDetect = vscode.workspace.getConfiguration('typescript.tsc').get('autoDetect'); + const autoDetect = vscode.workspace.getConfiguration('typescript.tsc').get('autoDetect'); if (this.taskProviderSub && autoDetect === 'off') { this.taskProviderSub.dispose(); this.taskProviderSub = undefined; - } else if (!this.taskProviderSub && autoDetect === 'on') { + } else if (!this.taskProviderSub && autoDetect !== 'off') { this.taskProviderSub = vscode.workspace.registerTaskProvider('typescript', new TscTaskProvider(this.lazyClient)); } } diff --git a/extensions/typescript/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript/syntaxes/TypeScript.tmLanguage.json index 28b955a0742..454dc854bdf 100644 --- a/extensions/typescript/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript/syntaxes/TypeScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/cd202a57fd738d6d2c712c2ed63f5f41a11eb558", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/f3a2069b99f45c34ac5cc7dc5f1dcb4e81486ab9", "name": "TypeScript", "scopeName": "source.ts", "fileTypes": [ @@ -272,7 +272,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.ts", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.ts entity.name.function.ts" @@ -506,7 +506,7 @@ } }, { - "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -733,7 +733,7 @@ }, { "name": "meta.definition.property.ts entity.name.function.ts", - "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))" + "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))" }, { "name": "meta.definition.property.ts variable.object.property.ts", @@ -996,7 +996,7 @@ }, { "name": "meta.arrow.ts", - "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -1701,8 +1701,7 @@ "include": "#comment" }, { - "comment": "(default|*|name) as alias", - "match": "(?x) (?: \\b(default)\\b | (\\*) | ([_$[:alpha:]][_$[:alnum:]]*)) \\s+\n (as) \\s+ (?: (\\b default \\b | \\*) | ([_$[:alpha:]][_$[:alnum:]]*))", + "match": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=:\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.ts" @@ -2037,13 +2033,13 @@ ] }, "function-call": { - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", - "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "patterns": [ { "name": "meta.function-call.ts", "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "end": "(?=\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "patterns": [ { "include": "#literal" @@ -2527,7 +2523,7 @@ } }, { - "match": "(?x) (\\.) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "match": "(?x) (\\.) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "captures": { "1": { "name": "punctuation.accessor.ts" @@ -2607,7 +2603,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(\\.)\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(\\.)\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.ts" @@ -3363,7 +3359,7 @@ "name": "punctuation.definition.group.regexp" }, "1": { - "name": "punctuation.definition.group.capture.regexp" + "name": "punctuation.definition.group.no-capture.regexp" } }, "end": "\\)", diff --git a/extensions/typescript/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript/syntaxes/TypeScriptReact.tmLanguage.json index 52273319c2e..3c825612994 100644 --- a/extensions/typescript/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript/syntaxes/TypeScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/d6ee336bf6047594768a56f955563ba5ce86e7c9", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/824f47ea6e98590ac2e75db5bebdf6eff71421ad", "name": "TypeScriptReact", "scopeName": "source.tsx", "fileTypes": [ @@ -275,7 +275,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.tsx", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.tsx entity.name.function.tsx" @@ -509,7 +509,7 @@ } }, { - "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -736,7 +736,7 @@ }, { "name": "meta.definition.property.tsx entity.name.function.tsx", - "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))" + "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))" }, { "name": "meta.definition.property.tsx variable.object.property.tsx", @@ -999,7 +999,7 @@ }, { "name": "meta.arrow.tsx", - "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -1704,8 +1704,7 @@ "include": "#comment" }, { - "comment": "(default|*|name) as alias", - "match": "(?x) (?: \\b(default)\\b | (\\*) | ([_$[:alpha:]][_$[:alnum:]]*)) \\s+\n (as) \\s+ (?: (\\b default \\b | \\*) | ([_$[:alpha:]][_$[:alnum:]]*))", + "match": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=:\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.tsx" @@ -2040,13 +2036,13 @@ ] }, "function-call": { - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", - "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "patterns": [ { "name": "meta.function-call.tsx", "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "end": "(?=\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "patterns": [ { "include": "#literal" @@ -2493,7 +2489,7 @@ } }, { - "match": "(?x) (\\.) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", + "match": "(?x) (\\.) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)?\\()", "captures": { "1": { "name": "punctuation.accessor.tsx" @@ -2573,7 +2569,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(\\.)\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(\\.)\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*[_$[:alpha:]\\{\\(\\[]([^<>=]|=[^<]|\\<\\s*[_$[:alpha:]\\{\\(\\[]([^=<>]|=[^<])+\\>)+>\\s*)? # typeparameters\n \\((\\s*[_$[:alpha:]\\{\\(]([^()]|\\((\\s*[_$[:alpha:]\\{\\(]\\{\\(][^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.tsx" @@ -3329,7 +3325,7 @@ "name": "punctuation.definition.group.regexp" }, "1": { - "name": "punctuation.definition.group.capture.regexp" + "name": "punctuation.definition.group.no-capture.regexp" } }, "end": "\\)", @@ -4101,6 +4097,7 @@ } }, "end": "(?=[/]?>)", + "contentName": "meta.tag.attributes.tsx", "patterns": [ { "include": "#comment" diff --git a/extensions/vb/language-configuration.json b/extensions/vb/language-configuration.json index 6ef9c3c4d48..d9a6b21014a 100644 --- a/extensions/vb/language-configuration.json +++ b/extensions/vb/language-configuration.json @@ -23,8 +23,8 @@ ], "folding": { "markers": { - "start": "^\\s*#Region", - "end": "^\\s*#End Region" + "start": "^\\s*#Region\\b", + "end": "^\\s*#End Region\\b" } } } \ No newline at end of file diff --git a/extensions/vscode-api-tests/src/window.test.ts b/extensions/vscode-api-tests/src/window.test.ts index df08d550a58..c197808fd9e 100644 --- a/extensions/vscode-api-tests/src/window.test.ts +++ b/extensions/vscode-api-tests/src/window.test.ts @@ -349,17 +349,17 @@ suite('window namespace tests', () => { return Promise.all([a, b]); }); - // test('showWorkspaceFolderPick', function () { - // const p = (window).showWorkspaceFolderPick(undefined); + test('showWorkspaceFolderPick', function () { + const p = (window).showWorkspaceFolderPick(undefined); - // return commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem').then(() => { - // return p.then(workspace => { - // assert.ok(true); - // }, error => { - // assert.ok(false); - // }); - // }); - // }); + return commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem').then(() => { + return p.then(workspace => { + assert.ok(true); + }, error => { + assert.ok(false); + }); + }); + }); test('Default value for showInput Box accepted even if fails validateInput, #33691', function () { const result = window.showInputBox({ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 17ee6622a1e..a801e584465 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -566,6 +566,16 @@ "from": "winreg@1.2.0", "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.0.tgz" }, + "windows-foreground-love": { + "version": "0.1.0", + "from": "windows-foreground-love@0.1.0", + "resolved": "https://registry.npmjs.org/windows-foreground-love/-/windows-foreground-love-0.1.0.tgz" + }, + "windows-mutex": { + "version": "0.2.0", + "from": "windows-mutex@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/windows-mutex/-/windows-mutex-0.2.0.tgz" + }, "windows-process-tree": { "version": "0.1.6", "from": "windows-process-tree@0.1.6", @@ -574,7 +584,7 @@ "xterm": { "version": "2.9.1", "from": "Tyriar/xterm.js#vscode-release/1.18", - "resolved": "git+https://github.com/Tyriar/xterm.js.git#b49fe4a329ca792e1eb59d647042e0460e860b4d" + "resolved": "git+https://github.com/Tyriar/xterm.js.git#d57ac8df05ddf04b509505eadc0d408a0b3bfa38" }, "yauzl": { "version": "2.8.0", @@ -582,4 +592,4 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.8.0.tgz" } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 2133570b711..417c30f2a2a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.18.0", - "electronVersion": "1.7.7", + "electronVersion": "1.7.9", "distro": "59a68f7780bb6571a1460ea00ca7b994126baeda", "author": { "name": "Microsoft Corporation" diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index 75bf1761b46..ce604fb1bf0 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 1.7.7 +// Type definitions for Electron 1.7.9 // Project: http://electron.atom.io/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions @@ -37,91 +37,81 @@ declare namespace Electron { shiftKey?: boolean; altKey?: boolean; } + interface CommonInterface { - clipboard: Electron.Clipboard; - crashReporter: Electron.CrashReporter; - nativeImage: typeof Electron.NativeImage; - screen: Electron.Screen; - shell: Electron.Shell; + clipboard: Clipboard; + crashReporter: CrashReporter; + nativeImage: typeof NativeImage; + screen: Screen; + shell: Shell; } interface MainInterface extends CommonInterface { - app: Electron.App; - autoUpdater: Electron.AutoUpdater; - BrowserView: typeof Electron.BrowserView; - BrowserWindow: typeof Electron.BrowserWindow; - ClientRequest: typeof Electron.ClientRequest; - contentTracing: Electron.ContentTracing; - Cookies: typeof Electron.Cookies; - Debugger: typeof Electron.Debugger; - dialog: Electron.Dialog; - DownloadItem: typeof Electron.DownloadItem; - globalShortcut: Electron.GlobalShortcut; - IncomingMessage: typeof Electron.IncomingMessage; - ipcMain: Electron.IpcMain; - Menu: typeof Electron.Menu; - MenuItem: typeof Electron.MenuItem; - net: Electron.Net; - Notification: typeof Electron.Notification; - powerMonitor: Electron.PowerMonitor; - powerSaveBlocker: Electron.PowerSaveBlocker; - protocol: Electron.Protocol; - session: typeof Electron.Session; - systemPreferences: Electron.SystemPreferences; - TouchBar: typeof Electron.TouchBar; - Tray: typeof Electron.Tray; - webContents: typeof Electron.WebContents; - WebRequest: typeof Electron.WebRequest; + app: App; + autoUpdater: AutoUpdater; + BrowserView: typeof BrowserView; + BrowserWindow: typeof BrowserWindow; + ClientRequest: typeof ClientRequest; + contentTracing: ContentTracing; + Cookies: typeof Cookies; + Debugger: typeof Debugger; + dialog: Dialog; + DownloadItem: typeof DownloadItem; + globalShortcut: GlobalShortcut; + IncomingMessage: typeof IncomingMessage; + ipcMain: IpcMain; + Menu: typeof Menu; + MenuItem: typeof MenuItem; + net: Net; + Notification: typeof Notification; + powerMonitor: PowerMonitor; + powerSaveBlocker: PowerSaveBlocker; + protocol: Protocol; + session: typeof Session; + systemPreferences: SystemPreferences; + TouchBar: typeof TouchBar; + Tray: typeof Tray; + webContents: typeof WebContents; + WebRequest: typeof WebRequest; } interface RendererInterface extends CommonInterface { - BrowserWindowProxy: typeof Electron.BrowserWindowProxy; - desktopCapturer: Electron.DesktopCapturer; - ipcRenderer: Electron.IpcRenderer; - remote: Electron.Remote; - webFrame: Electron.WebFrame; - webviewTag: Electron.WebviewTag; + BrowserWindowProxy: typeof BrowserWindowProxy; + desktopCapturer: DesktopCapturer; + ipcRenderer: IpcRenderer; + remote: Remote; + webFrame: WebFrame; + webviewTag: WebviewTag; } - interface AllElectron { - app: Electron.App; - autoUpdater: Electron.AutoUpdater; - BrowserView: typeof Electron.BrowserView; - BrowserWindow: typeof Electron.BrowserWindow; - BrowserWindowProxy: typeof Electron.BrowserWindowProxy; - ClientRequest: typeof Electron.ClientRequest; - clipboard: Electron.Clipboard; - contentTracing: Electron.ContentTracing; - Cookies: typeof Electron.Cookies; - crashReporter: Electron.CrashReporter; - Debugger: typeof Electron.Debugger; - desktopCapturer: Electron.DesktopCapturer; - dialog: Electron.Dialog; - DownloadItem: typeof Electron.DownloadItem; - globalShortcut: Electron.GlobalShortcut; - IncomingMessage: typeof Electron.IncomingMessage; - ipcMain: Electron.IpcMain; - ipcRenderer: Electron.IpcRenderer; - Menu: typeof Electron.Menu; - MenuItem: typeof Electron.MenuItem; - nativeImage: typeof Electron.NativeImage; - net: Electron.Net; - Notification: typeof Electron.Notification; - powerMonitor: Electron.PowerMonitor; - powerSaveBlocker: Electron.PowerSaveBlocker; - protocol: Electron.Protocol; - remote: Electron.Remote; - screen: Electron.Screen; - session: typeof Electron.Session; - shell: Electron.Shell; - systemPreferences: Electron.SystemPreferences; - TouchBar: typeof Electron.TouchBar; - Tray: typeof Electron.Tray; - webContents: typeof Electron.WebContents; - webFrame: Electron.WebFrame; - WebRequest: typeof Electron.WebRequest; - webviewTag: Electron.WebviewTag; - } + interface AllElectron extends MainInterface, RendererInterface { } + + const app: App; + const autoUpdater: AutoUpdater; + const clipboard: Clipboard; + const contentTracing: ContentTracing; + const crashReporter: CrashReporter; + const desktopCapturer: DesktopCapturer; + const dialog: Dialog; + const globalShortcut: GlobalShortcut; + const ipcMain: IpcMain; + const ipcRenderer: IpcRenderer; + type nativeImage = NativeImage; + const nativeImage: typeof NativeImage; + const net: Net; + const powerMonitor: PowerMonitor; + const powerSaveBlocker: PowerSaveBlocker; + const protocol: Protocol; + const remote: Remote; + const screen: Screen; + type session = Session; + const session: typeof Session; + const shell: Shell; + const systemPreferences: SystemPreferences; + type webContents = WebContents; + const webContents: typeof WebContents; + const webFrame: WebFrame; + const webviewTag: WebviewTag; interface App extends EventEmitter { @@ -6362,7 +6352,8 @@ declare namespace Electron { ignoreSystemCrashHandler?: boolean; /** * An object you can define that will be sent along with the report. Only string - * properties are sent correctly. Nested objects are not supported. + * properties are sent correctly. Nested objects are not supported and the property + * names and values must be less than 64 characters long. */ extra?: any; /** @@ -8089,12 +8080,11 @@ declare namespace Electron { } declare module 'electron' { - const electron: Electron.AllElectron; - export = electron; + export = Electron; } interface NodeRequireFunction { - (moduleName: 'electron'): Electron.AllElectron; + (moduleName: 'electron'): typeof Electron; } interface File { diff --git a/src/typings/xterm.d.ts b/src/typings/xterm.d.ts index 197f2e7ebc8..ceed656bf3f 100644 --- a/src/typings/xterm.d.ts +++ b/src/typings/xterm.d.ts @@ -41,6 +41,11 @@ interface ITerminalOptions { */ disableStdin?: boolean; + /** + * Whether to enable the rendering of bold text. + */ + enableBold?: boolean; + /** * The font size used to render text. */ @@ -397,7 +402,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; + getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -447,7 +452,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 930ab74fd19..06e582d642e 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -21,6 +21,10 @@ display: inline-block; } +.monaco-action-bar.reverse .actions-container { + flex-direction: row-reverse; +} + .monaco-action-bar .action-item { cursor: pointer; display: inline-block; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 688cb903a45..de30d225bc9 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -350,8 +350,10 @@ export class ActionItem extends BaseActionItem { } export enum ActionsOrientation { - HORIZONTAL = 1, - VERTICAL = 2 + HORIZONTAL, + HORIZONTAL_REVERSE, + VERTICAL, + VERTICAL_REVERSE, } export interface IActionItemProvider { @@ -420,18 +422,38 @@ export class ActionBar extends EventEmitter implements IActionRunner { DOM.addClass(this.domNode, 'animated'); } - let isVertical = this.options.orientation === ActionsOrientation.VERTICAL; - if (isVertical) { - this.domNode.className += ' vertical'; + let previousKey: KeyCode; + let nextKey: KeyCode; + + switch (this.options.orientation) { + case ActionsOrientation.HORIZONTAL: + previousKey = KeyCode.LeftArrow; + nextKey = KeyCode.RightArrow; + break; + case ActionsOrientation.HORIZONTAL_REVERSE: + previousKey = KeyCode.RightArrow; + nextKey = KeyCode.LeftArrow; + this.domNode.className += ' reverse'; + break; + case ActionsOrientation.VERTICAL: + previousKey = KeyCode.UpArrow; + nextKey = KeyCode.DownArrow; + this.domNode.className += ' vertical'; + break; + case ActionsOrientation.VERTICAL_REVERSE: + previousKey = KeyCode.DownArrow; + nextKey = KeyCode.UpArrow; + this.domNode.className += ' vertical reverse'; + break; } $(this.domNode).on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); let eventHandled = true; - if (event.equals(isVertical ? KeyCode.UpArrow : KeyCode.LeftArrow)) { + if (event.equals(previousKey)) { this.focusPrevious(); - } else if (event.equals(isVertical ? KeyCode.DownArrow : KeyCode.RightArrow)) { + } else if (event.equals(nextKey)) { this.focusNext(); } else if (event.equals(KeyCode.Escape)) { this.cancel(); diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index fb83ca1d3b1..89dac41da35 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -18,11 +18,17 @@ export interface IIconLabelCreationOptions { supportHighlights?: boolean; } +export interface ILabelBadgeOptions { + title: string; + className: string; +} + export interface IIconLabelOptions { title?: string; extraClasses?: string[]; italic?: boolean; matches?: IMatch[]; + badge?: ILabelBadgeOptions; } class FastLabelNode { @@ -84,6 +90,7 @@ export class IconLabel { private domNode: FastLabelNode; private labelNode: FastLabelNode | HighlightedLabel; private descriptionNode: FastLabelNode; + private badgeNode: HTMLSpanElement; constructor(container: HTMLElement, options?: IIconLabelCreationOptions) { this.domNode = new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))); @@ -141,6 +148,20 @@ export class IconLabel { this.descriptionNode.textContent = description || ''; this.descriptionNode.empty = !description; + + if (options && options.badge) { + if (!this.badgeNode) { + this.badgeNode = document.createElement('span'); + this.element.style.display = 'flex'; + this.element.appendChild(this.badgeNode); + } + this.badgeNode.title = options.badge.title; + this.badgeNode.className = `label-badge ${options.badge.className}`; + dom.show(this.badgeNode); + + } else if (this.badgeNode) { + dom.hide(this.badgeNode); + } } public dispose(): void { @@ -163,4 +184,4 @@ export class FileLabel extends IconLabel { this.setValue(paths.basename(file.fsPath), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: file.fsPath }); } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index 43078b1706b..259040da2c2 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -42,4 +42,17 @@ .monaco-icon-label.italic > .label-name, .monaco-icon-label.italic > .label-description { font-style: italic; -} \ No newline at end of file +} + +.monaco-icon-label > .label-badge { + align-self: center; + height: 12px; + min-width: 10px; + line-height: 125%; + font-size: 80%; + margin: 1px 15px 1px auto; + padding: 2px 3px; + border-radius: 5px; + font-weight: normal; + text-align: center; +} diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css index e4853977a88..50a7d87499a 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/panelview.css @@ -53,8 +53,8 @@ } /* TODO: actions should be part of the panel, but they aren't yet */ -.monaco-panel-view .panel:hover > .panel-header > .actions, -.monaco-panel-view .panel > .panel-header.focused > .actions { +.monaco-panel-view .panel:hover > .panel-header.expanded > .actions, +.monaco-panel-view .panel > .panel-header.focused.expanded > .actions { display: initial; } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 983ca30b520..e2120526002 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -13,7 +13,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; -function isThenable(obj: any): obj is Thenable { +export function isThenable(obj: any): obj is Thenable { return obj && typeof (>obj).then === 'function'; } diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 67f5ce6ade6..3571fb9c33e 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -450,7 +450,7 @@ export function parse(arg1: string | IExpression | IRelativePattern, options: IG } // Glob with String - if (typeof arg1 === 'string' || (arg1 as IRelativePattern).base) { + if (typeof arg1 === 'string' || isRelativePattern(arg1)) { const parsedPattern = parsePattern(arg1 as string | IRelativePattern, options); if (parsedPattern === NULL) { return FALSE; @@ -471,6 +471,12 @@ export function parse(arg1: string | IExpression | IRelativePattern, options: IG return parsedExpression(arg1, options); } +function isRelativePattern(obj: any): obj is IRelativePattern { + const rp = obj as IRelativePattern; + + return typeof rp.base === 'string' && typeof rp.pattern === 'string'; +} + /** * Same as `parse`, but the ParsedExpression is guaranteed to return a Promise */ diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 2aa8c50c9f7..538858e0c0d 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -6,6 +6,7 @@ export interface IJSONSchema { id?: string; + $id?: string; $schema?: string; type?: string | string[]; title?: string; @@ -17,7 +18,7 @@ export interface IJSONSchema { additionalProperties?: boolean | IJSONSchema; minProperties?: number; maxProperties?: number; - dependencies?: IJSONSchemaMap | { [name: string]: string[] }; + dependencies?: IJSONSchemaMap | { [prop: string]: string[] }; items?: IJSONSchema | IJSONSchema[]; minItems?: number; maxItems?: number; @@ -28,8 +29,8 @@ export interface IJSONSchema { maxLength?: number; minimum?: number; maximum?: number; - exclusiveMinimum?: boolean; - exclusiveMaximum?: boolean; + exclusiveMinimum?: boolean | number; + exclusiveMaximum?: boolean | number; multipleOf?: number; required?: string[]; $ref?: string; @@ -40,11 +41,19 @@ export interface IJSONSchema { enum?: any[]; format?: string; + // schema draft 06 + const?: any; + contains?: IJSONSchema; + propertyNames?: IJSONSchema; + + // VSCode extensions defaultSnippets?: IJSONSchemaSnippet[]; // VSCode extension errorMessage?: string; // VSCode extension patternErrorMessage?: string; // VSCode extension deprecationMessage?: string; // VSCode extension enumDescriptions?: string[]; // VSCode extension + markdownEnumDescriptions?: string[]; // VSCode extension + markdownDescription?: string; // VSCode extension doNotSuggest?: boolean; // VSCode extension } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 72284c76d1a..bb7f231500b 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -323,6 +323,10 @@ export class TernarySearchTree { this._segments = segments; } + clear(): void { + this._root = undefined; + } + set(key: string, element: E): void { const segements = this._segments.reset(key); this._root = this._set(this._root, segements.next(), segements, element); @@ -448,11 +452,11 @@ export class TernarySearchTree { } } - forEach(callback: (entry: [string, E]) => any) { + forEach(callback: (value: E, index: string) => any) { this._forEach(this._root, [], callback); } - private _forEach(node: TernarySearchTreeNode, parts: string[], callback: (entry: [string, E]) => any) { + private _forEach(node: TernarySearchTreeNode, parts: string[], callback: (value: E, index: string) => any) { if (!node) { return; } @@ -461,7 +465,7 @@ export class TernarySearchTree { let newParts = parts.slice(); newParts.push(node.str); if (node.element) { - callback([this._segments.join(newParts), node.element]); + callback(node.element, this._segments.join(newParts)); } this._forEach(node.mid, newParts, callback); } diff --git a/src/vs/base/common/numbers.ts b/src/vs/base/common/numbers.ts index 9f804fe6dd9..f23b941b7fe 100644 --- a/src/vs/base/common/numbers.ts +++ b/src/vs/base/common/numbers.ts @@ -50,4 +50,8 @@ export function countToArray(fromOrTo: number, to?: number): number[] { export function clamp(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max); +} + +export function rot(index: number, modulo: number): number { + return (modulo + (index % modulo)) % modulo; } \ No newline at end of file diff --git a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts b/src/vs/base/parts/quickopen/common/quickOpenScorer.ts index 1dce6a05bad..a479c16aa0a 100644 --- a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts +++ b/src/vs/base/parts/quickopen/common/quickOpenScorer.ts @@ -8,12 +8,91 @@ import { compareAnything } from 'vs/base/common/comparers'; import { matchesPrefix, IMatch, createMatches, matchesCamelCase, isSeparatorAtPos, isUpper } from 'vs/base/common/filters'; import { isEqual, nativeSep } from 'vs/base/common/paths'; +import { isWindows } from 'vs/base/common/platform'; +import { stripWildcards } from 'vs/base/common/strings'; export type Score = [number /* score */, number[] /* match positions */]; export type ScorerCache = { [key: string]: IItemScore }; const NO_SCORE: Score = [0, []]; +export function _doScore(target: string, query: string, queryLower: string, fuzzy: boolean): Score { + if (!target || !query) { + return NO_SCORE; // return early if target or query are undefined + } + + if (target.length < query.length) { + return NO_SCORE; // impossible for query to be contained in target + } + + // console.group(`Target: ${target}, Query: ${query}`); + + const queryLen = query.length; + const targetLower = target.toLowerCase(); + + let res = NO_SCORE; + + // When not searching fuzzy, we require the query to be contained fully + // in the target string. We set the offset to search from to that location. + if (!fuzzy) { + const indexOfQueryInTarget = targetLower.indexOf(queryLower); + if (indexOfQueryInTarget === -1) { + // console.log(`Characters not matching consecutively ${queryLower} within ${targetLower}`); + + return NO_SCORE; + } + + res = _doScoreFromOffset(target, query, targetLower, queryLower, queryLen, indexOfQueryInTarget); + } + + // When searching fuzzy we run the scorer for each location of the first query + // character so that we can produce better results in case the pattern matches + // multiple times on the target (prevent scattering of matching positions). + else { + const queryFirstCharacter = queryLower[0]; + + let offset = 0; + while ((offset = targetLower.indexOf(queryFirstCharacter, offset)) !== -1) { + const scoreFromOffset = _doScoreFromOffset(target, query, targetLower, queryLower, queryLen, offset); + if (isBetterScore(res, scoreFromOffset)) { + res = scoreFromOffset; + } + + offset++; + } + } + + // console.log(`%cFinal Score: ${score}`, 'font-weight: bold'); + // console.groupEnd(); + + return res; +} + +function isBetterScore(score: Score, candidate: Score): boolean { + if (candidate[0] > score[0]) { + return true; // candidate has higher score + } + + if (score[0] > candidate[0]) { + return false; // candidate has lower score + } + + // Score is the same, check by match compactness + const matchStart = score[1][0]; + const matchEnd = score[1][score[1].length - 1]; + const matchLength = matchEnd - matchStart; + + const candidateMatchStart = candidate[1][0]; + const candidateMatchEnd = candidate[1][candidate[1].length - 1]; + const candidateMatchLength = candidateMatchEnd - candidateMatchStart; + + if (candidateMatchLength < matchLength) { + return true; // candidate has more compact matches + } + + return false; +} + // Based on material from: /*! BEGIN THIRD PARTY @@ -31,81 +110,18 @@ BEGIN THIRD PARTY * Date: Tue Mar 1 2011 * Updated: Tue Mar 10 2015 */ - -/** - * Compute a score for the given string and the given query. - * - * Rules: - * Character score: 1 - * Same case bonus: 1 - * Upper case bonus: 1 - * Consecutive match bonus: 5 - * Start of word/path bonus: 7 - * Start of string bonus: 8 - */ -export function _doScore(target: string, query: string, fuzzy: boolean, inverse?: boolean): Score { - if (!target || !query) { - return NO_SCORE; // return early if target or query are undefined - } - - if (target.length < query.length) { - return NO_SCORE; // impossible for query to be contained in target - } - - // console.group(`Target: ${target}, Query: ${query}`); - - const queryLen = query.length; - const targetLower = target.toLowerCase(); - const queryLower = query.toLowerCase(); - +function _doScoreFromOffset(target: string, query: string, targetLower: string, queryLower: string, queryLen: number, offset: number): Score { const matchingPositions: number[] = []; - let index: number; - let startAt: number; - if (!inverse) { - index = 0; - startAt = 0; - } else { - index = queryLen - 1; // inverse: from end of query to beginning - startAt = target.length - 1; // inverse: from end of target to beginning - } - - // When not searching fuzzy, we require the query to be contained fully - // in the target string. - if (!fuzzy) { - let indexOfQueryInTarget: number; - if (!inverse) { - indexOfQueryInTarget = targetLower.indexOf(queryLower); - } else { - indexOfQueryInTarget = targetLower.lastIndexOf(queryLower); - } - - if (indexOfQueryInTarget === -1) { - // console.log(`Characters not matching consecutively ${queryLower} within ${targetLower}`); - - return NO_SCORE; - } - - // Adjust the start position with the offset of the query - if (!inverse) { - startAt = indexOfQueryInTarget; - } else { - startAt = indexOfQueryInTarget + query.length; - } - } - + let targetIndex = offset; + let queryIndex = 0; let score = 0; - while (inverse ? index >= 0 : index < queryLen) { + while (queryIndex < queryLen) { // Check for query character being contained in target - let indexOf: number; - if (!inverse) { - indexOf = targetLower.indexOf(queryLower[index], startAt); - } else { - indexOf = targetLower.lastIndexOf(queryLower[index], startAt); // inverse: look from the end - } + const indexOfQueryInTarget = targetLower.indexOf(queryLower[queryIndex], targetIndex); - if (indexOf < 0) { + if (indexOfQueryInTarget < 0) { // console.log(`Character not part of target ${query[index]}`); score = 0; @@ -113,7 +129,7 @@ export function _doScore(target: string, query: string, fuzzy: boolean, inverse? } // Fill into positions array - matchingPositions.push(indexOf); + matchingPositions.push(indexOfQueryInTarget); // Character match bonus score += 1; @@ -121,35 +137,35 @@ export function _doScore(target: string, query: string, fuzzy: boolean, inverse? // console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${query[index]} at index ${indexOf}, total score: ${score})`, 'font-weight: normal'); // Consecutive match bonus - if (startAt === indexOf && index > 0) { + if (targetIndex === indexOfQueryInTarget && queryIndex > 0) { score += 5; // console.log('Consecutive match bonus: +5'); } // Same case bonus - if (target[indexOf] === query[index]) { + if (target[indexOfQueryInTarget] === query[queryIndex]) { score += 1; // console.log('Same case bonus: +1'); } // Start of word bonus - if (indexOf === 0) { + if (indexOfQueryInTarget === 0) { score += 8; // console.log('Start of word bonus: +8'); } // After separator bonus - else if (isSeparatorAtPos(target, indexOf - 1)) { + else if (isSeparatorAtPos(target, indexOfQueryInTarget - 1)) { score += 7; // console.log('After separtor bonus: +7'); } // Inside word upper case bonus - else if (isUpper(target.charCodeAt(indexOf))) { + else if (isUpper(target.charCodeAt(indexOfQueryInTarget))) { score += 1; // console.log('Inside word upper case bonus: +1'); @@ -157,18 +173,8 @@ export function _doScore(target: string, query: string, fuzzy: boolean, inverse? // console.groupEnd(); - if (!inverse) { - startAt = indexOf + 1; - index++; - } else { - startAt = indexOf - 1; // inverse: go to begining from end - index--; // inverse: also for query index - } - } - - // inverse: flip the matching positions so that they appear in order - if (inverse) { - matchingPositions.reverse(); + targetIndex = indexOfQueryInTarget + 1; + queryIndex++; } const res: Score = (score > 0) ? [score, matchingPositions] : NO_SCORE; @@ -229,8 +235,34 @@ const LABEL_PREFIX_SCORE = 1 << 17; const LABEL_CAMELCASE_SCORE = 1 << 16; const LABEL_SCORE_THRESHOLD = 1 << 15; -export function scoreItem(item: T, query: string, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache): IItemScore { - if (!item || !query) { +export interface IPreparedQuery { + value: string; + lowercase: string; + containsPathSeparator: boolean; +} + +/** + * Helper function to prepare a search value for scoring in quick open by removing unwanted characters. + */ +export function prepareQuery(value: string): IPreparedQuery { + let lowercase: string; + let containsPathSeparator: boolean; + + if (value) { + value = stripWildcards(value).replace(/\s/g, ''); // get rid of all wildcards and whitespace + if (isWindows) { + value = value.replace(/\//g, '\\'); // Help Windows users to search for paths when using slash + } + + lowercase = value.toLowerCase(); + containsPathSeparator = value.indexOf(nativeSep) >= 0; + } + + return { value, lowercase, containsPathSeparator }; +} + +export function scoreItem(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache): IItemScore { + if (!item || !query.value) { return NO_ITEM_SCORE; // we need an item and query to score on at least } @@ -243,9 +275,9 @@ export function scoreItem(item: T, query: string, fuzzy: boolean, accessor: I let cacheHash: string; if (description) { - cacheHash = `${label}${description}${query}${fuzzy}`; + cacheHash = `${label}${description}${query.value}${fuzzy}`; } else { - cacheHash = `${label}${query}${fuzzy}`; + cacheHash = `${label}${query.value}${fuzzy}`; } const cached = cache[cacheHash]; @@ -259,29 +291,34 @@ export function scoreItem(item: T, query: string, fuzzy: boolean, accessor: I return itemScore; } -function doScoreItem(label: string, description: string, path: string, query: string, fuzzy: boolean): IItemScore { +function doScoreItem(label: string, description: string, path: string, query: IPreparedQuery, fuzzy: boolean): IItemScore { // 1.) treat identity matches on full path highest - if (path && isEqual(query, path, true)) { + if (path && isEqual(query.value, path, true)) { return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : void 0 }; } - // 2.) treat prefix matches on the label second highest - const prefixLabelMatch = matchesPrefix(query, label); - if (prefixLabelMatch) { - return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch }; - } + // We only consider label matches if the query is not including file path separators + const preferLabelMatches = !path || !query.containsPathSeparator; + if (preferLabelMatches) { - // 3.) treat camelcase matches on the label third highest - const camelcaseLabelMatch = matchesCamelCase(query, label); - if (camelcaseLabelMatch) { - return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch }; - } + // 2.) treat prefix matches on the label second highest + const prefixLabelMatch = matchesPrefix(query.value, label); + if (prefixLabelMatch) { + return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch }; + } - // 4.) prefer scores on the label if any - const [labelScore, labelPositions] = _doScore(label, query, fuzzy); - if (labelScore) { - return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) }; + // 3.) treat camelcase matches on the label third highest + const camelcaseLabelMatch = matchesCamelCase(query.value, label); + if (camelcaseLabelMatch) { + return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch }; + } + + // 4.) prefer scores on the label if any + const [labelScore, labelPositions] = _doScore(label, query.value, query.lowercase, fuzzy); + if (labelScore) { + return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) }; + } } // 5.) finally compute description + label scores if we have a description @@ -294,18 +331,7 @@ function doScoreItem(label: string, description: string, path: string, query: const descriptionPrefixLength = descriptionPrefix.length; const descriptionAndLabel = `${descriptionPrefix}${label}`; - let [labelDescriptionScore, labelDescriptionPositions] = _doScore(descriptionAndLabel, query, fuzzy); - - // Optimize for file paths: score from the back to the beginning to catch more specific folder - // names that match on the end of the file. This yields better results in most cases. - if (!!path) { - const [labelDescriptionScoreInverse, labelDescriptionPositionsInverse] = _doScore(descriptionAndLabel, query, fuzzy, true /* inverse */); - if (labelDescriptionScoreInverse && labelDescriptionScoreInverse > labelDescriptionScore) { - labelDescriptionScore = labelDescriptionScoreInverse; - labelDescriptionPositions = labelDescriptionPositionsInverse; - } - } - + const [labelDescriptionScore, labelDescriptionPositions] = _doScore(descriptionAndLabel, query.value, query.lowercase, fuzzy); if (labelDescriptionScore) { const labelDescriptionMatches = createMatches(labelDescriptionPositions); const labelMatch: IMatch[] = []; @@ -338,18 +364,21 @@ function doScoreItem(label: string, description: string, path: string, query: return NO_ITEM_SCORE; } -export function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache, fallbackComparer = fallbackCompare): number { - const scoreA = scoreItem(itemA, query, fuzzy, accessor, cache).score; - const scoreB = scoreItem(itemB, query, fuzzy, accessor, cache).score; +export function compareItemsByScore(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache, fallbackComparer = fallbackCompare): number { + const itemScoreA = scoreItem(itemA, query, fuzzy, accessor, cache); + const itemScoreB = scoreItem(itemB, query, fuzzy, accessor, cache); - // 1.) check for identity matches + const scoreA = itemScoreA.score; + const scoreB = itemScoreB.score; + + // 1.) prefer identity matches if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) { if (scoreA !== scoreB) { return scoreA === PATH_IDENTITY_SCORE ? -1 : 1; } } - // 2.) check for label prefix matches + // 2.) prefer label prefix matches if (scoreA === LABEL_PREFIX_SCORE || scoreB === LABEL_PREFIX_SCORE) { if (scoreA !== scoreB) { return scoreA === LABEL_PREFIX_SCORE ? -1 : 1; @@ -364,7 +393,7 @@ export function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: } } - // 3.) check for camelcase matches + // 3.) prefer camelcase matches if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) { if (scoreA !== scoreB) { return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1; @@ -373,13 +402,19 @@ export function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: const labelA = accessor.getItemLabel(itemA); const labelB = accessor.getItemLabel(itemB); + // prefer more compact camel case matches over longer + const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch); + if (comparedByMatchLength !== 0) { + return comparedByMatchLength; + } + // prefer shorter names when both match on label camelcase if (labelA.length !== labelB.length) { return labelA.length - labelB.length; } } - // 4.) check for label scores + // 4.) prefer label scores if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) { if (scoreB < LABEL_SCORE_THRESHOLD) { return -1; @@ -390,16 +425,89 @@ export function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: } } - // 5.) check for path scores + // 5.) compare by score if (scoreA !== scoreB) { return scoreA > scoreB ? -1 : 1; } - // 6.) at this point, scores are identical for both items so we start to use the fallback compare + // 6.) scores are identical, prefer more compact matches (label and description) + const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor); + const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor); + if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) { + return itemBMatchDistance > itemAMatchDistance ? -1 : 1; + } + + // 7.) at this point, scores are identical and match compactness as well + // for both items so we start to use the fallback compare return fallbackComparer(itemA, itemB, query, accessor); } -export function fallbackCompare(itemA: T, itemB: T, query: string, accessor: IItemAccessor): number { +function computeLabelAndDescriptionMatchDistance(item: T, score: IItemScore, accessor: IItemAccessor): number { + const hasLabelMatches = (score.labelMatch && score.labelMatch.length); + const hasDescriptionMatches = (score.descriptionMatch && score.descriptionMatch.length); + + let matchStart: number = -1; + let matchEnd: number = -1; + + // If we have description matches, the start is first of description match + if (hasDescriptionMatches) { + matchStart = score.descriptionMatch[0].start; + } + + // Otherwise, the start is the first label match + else if (hasLabelMatches) { + matchStart = score.labelMatch[0].start; + } + + // If we have label match, the end is the last label match + // If we had a description match, we add the length of the description + // as offset to the end to indicate this. + if (hasLabelMatches) { + matchEnd = score.labelMatch[score.labelMatch.length - 1].end; + if (hasDescriptionMatches) { + const itemDescription = accessor.getItemDescription(item); + if (itemDescription) { + matchEnd += itemDescription.length; + } + } + } + + // If we have just a description match, the end is the last description match + else if (hasDescriptionMatches) { + matchEnd = score.descriptionMatch[score.descriptionMatch.length - 1].end; + } + + return matchEnd - matchStart; +} + +function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number { + if ((!matchesA && !matchesB) || (!matchesA.length && !matchesB.length)) { + return 0; // make sure to not cause bad comparing when matches are not provided + } + + if (!matchesB || !matchesB.length) { + return -1; + } + + if (!matchesA || !matchesA.length) { + return 1; + } + + // Compute match length of A (first to last match) + const matchStartA = matchesA[0].start; + const matchEndA = matchesA[matchesA.length - 1].end; + const matchLengthA = matchEndA - matchStartA; + + // Compute match length of B (first to last match) + const matchStartB = matchesB[0].start; + const matchEndB = matchesB[matchesB.length - 1].end; + const matchLengthB = matchEndB - matchStartB; + + // Prefer shorter match length + return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1; +} + +export function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor): number { // check for label + description length and prefer shorter const labelA = accessor.getItemLabel(itemA); @@ -427,17 +535,17 @@ export function fallbackCompare(itemA: T, itemB: T, query: string, accessor: // compare by label if (labelA !== labelB) { - return compareAnything(labelA, labelB, query); + return compareAnything(labelA, labelB, query.value); } // compare by description if (descriptionA && descriptionB && descriptionA !== descriptionB) { - return compareAnything(descriptionA, descriptionB, query); + return compareAnything(descriptionA, descriptionB, query.value); } // compare by path if (pathA && pathB && pathA !== pathB) { - return compareAnything(pathA, pathB, query); + return compareAnything(pathA, pathB, query.value); } // equal diff --git a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts index 5af281048e2..877f8a0caa0 100644 --- a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts +++ b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts @@ -8,7 +8,8 @@ import * as assert from 'assert'; import * as scorer from 'vs/base/parts/quickopen/common/quickOpenScorer'; import URI from 'vs/base/common/uri'; -import { basename, dirname } from 'vs/base/common/paths'; +import { basename, dirname, nativeSep } from 'vs/base/common/paths'; +import { isWindows } from 'vs/base/common/platform'; class ResourceAccessorClass implements scorer.IItemAccessor { @@ -42,28 +43,44 @@ class NullAccessorClass implements scorer.IItemAccessor { } } +function _doScore(target: string, query: string, fuzzy: boolean): scorer.Score { + return scorer._doScore(target, query, query.toLowerCase(), fuzzy); +} + +function scoreItem(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.ScorerCache): scorer.IItemScore { + return scorer.scoreItem(item, scorer.prepareQuery(query), fuzzy, accessor, cache); +} + +function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.ScorerCache, fallbackComparer = scorer.fallbackCompare): number { + return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer); +} + const NullAccessor = new NullAccessorClass(); -const cache: scorer.ScorerCache = Object.create(null); +let cache: scorer.ScorerCache = Object.create(null); suite('Quick Open Scorer', () => { + setup(() => { + cache = Object.create(null); + }); + test('score (fuzzy)', function () { const target = 'HeLlo-World'; const scores: scorer.Score[] = []; - scores.push(scorer._doScore(target, 'HelLo-World', true)); // direct case match - scores.push(scorer._doScore(target, 'hello-world', true)); // direct mix-case match - scores.push(scorer._doScore(target, 'HW', true)); // direct case prefix (multiple) - scores.push(scorer._doScore(target, 'hw', true)); // direct mix-case prefix (multiple) - scores.push(scorer._doScore(target, 'H', true)); // direct case prefix - scores.push(scorer._doScore(target, 'h', true)); // direct mix-case prefix - scores.push(scorer._doScore(target, 'W', true)); // direct case word prefix - scores.push(scorer._doScore(target, 'w', true)); // direct mix-case word prefix - scores.push(scorer._doScore(target, 'Ld', true)); // in-string case match (multiple) - scores.push(scorer._doScore(target, 'ld', true)); // in-string mix-case match - scores.push(scorer._doScore(target, 'L', true)); // in-string case match - scores.push(scorer._doScore(target, 'l', true)); // in-string mix-case match - scores.push(scorer._doScore(target, '4', true)); // no match + scores.push(_doScore(target, 'HelLo-World', true)); // direct case match + scores.push(_doScore(target, 'hello-world', true)); // direct mix-case match + scores.push(_doScore(target, 'HW', true)); // direct case prefix (multiple) + scores.push(_doScore(target, 'hw', true)); // direct mix-case prefix (multiple) + scores.push(_doScore(target, 'H', true)); // direct case prefix + scores.push(_doScore(target, 'h', true)); // direct mix-case prefix + scores.push(_doScore(target, 'ld', true)); // in-string mix-case match (consecutive, avoids scattered hit) + scores.push(_doScore(target, 'W', true)); // direct case word prefix + scores.push(_doScore(target, 'w', true)); // direct mix-case word prefix + scores.push(_doScore(target, 'Ld', true)); // in-string case match (multiple) + scores.push(_doScore(target, 'L', true)); // in-string case match + scores.push(_doScore(target, 'l', true)); // in-string mix-case match + scores.push(_doScore(target, '4', true)); // no match // Assert scoring order let sortedScores = scores.concat().sort((a, b) => b[0] - a[0]); @@ -82,42 +99,28 @@ suite('Quick Open Scorer', () => { test('score (non fuzzy)', function () { const target = 'HeLlo-World'; - assert.ok(scorer._doScore(target, 'HelLo-World', false)[0] > 0); - assert.equal(scorer._doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length); + assert.ok(_doScore(target, 'HelLo-World', false)[0] > 0); + assert.equal(_doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length); - assert.ok(scorer._doScore(target, 'hello-world', false)[0] > 0); - assert.equal(scorer._doScore(target, 'HW', false)[0], 0); - assert.ok(scorer._doScore(target, 'h', false)[0] > 0); - assert.ok(scorer._doScore(target, 'ello', false)[0] > 0); - assert.ok(scorer._doScore(target, 'ld', false)[0] > 0); - assert.equal(scorer._doScore(target, 'eo', false)[0], 0); - }); - - test('score (non fuzzy, inverse)', function () { - const target = 'HeLlo-World'; - - assert.ok(scorer._doScore(target, 'HelLo-World', false, true)[0] > 0); - assert.equal(scorer._doScore(target, 'HelLo-World', false, true)[1].length, 'HelLo-World'.length); - - assert.ok(scorer._doScore(target, 'hello-world', false, true)[0] > 0); - assert.equal(scorer._doScore(target, 'HW', false, true)[0], 0); - assert.ok(scorer._doScore(target, 'h', false, true)[0] > 0); - assert.ok(scorer._doScore(target, 'ello', false, true)[0] > 0); - assert.ok(scorer._doScore(target, 'ld', false, true)[0] > 0); - assert.equal(scorer._doScore(target, 'eo', false, true)[0], 0); + assert.ok(_doScore(target, 'hello-world', false)[0] > 0); + assert.equal(_doScore(target, 'HW', false)[0], 0); + assert.ok(_doScore(target, 'h', false)[0] > 0); + assert.ok(_doScore(target, 'ello', false)[0] > 0); + assert.ok(_doScore(target, 'ld', false)[0] > 0); + assert.equal(_doScore(target, 'eo', false)[0], 0); }); test('scoreItem - matches are proper', function () { - let res = scorer.scoreItem(null, 'something', true, ResourceAccessor, cache); + let res = scoreItem(null, 'something', true, ResourceAccessor, cache); assert.ok(!res.score); const resource = URI.file('/xyz/some/path/someFile123.txt'); - res = scorer.scoreItem(resource, 'something', true, NullAccessor, cache); + res = scoreItem(resource, 'something', true, NullAccessor, cache); assert.ok(!res.score); // Path Identity - const identityRes = scorer.scoreItem(resource, ResourceAccessor.getItemPath(resource), true, ResourceAccessor, cache); + const identityRes = scoreItem(resource, ResourceAccessor.getItemPath(resource), true, ResourceAccessor, cache); assert.ok(identityRes.score); assert.equal(identityRes.descriptionMatch.length, 1); assert.equal(identityRes.labelMatch.length, 1); @@ -127,7 +130,7 @@ suite('Quick Open Scorer', () => { assert.equal(identityRes.labelMatch[0].end, ResourceAccessor.getItemLabel(resource).length); // Basename Prefix - const basenamePrefixRes = scorer.scoreItem(resource, 'som', true, ResourceAccessor, cache); + const basenamePrefixRes = scoreItem(resource, 'som', true, ResourceAccessor, cache); assert.ok(basenamePrefixRes.score); assert.ok(!basenamePrefixRes.descriptionMatch); assert.equal(basenamePrefixRes.labelMatch.length, 1); @@ -135,7 +138,7 @@ suite('Quick Open Scorer', () => { assert.equal(basenamePrefixRes.labelMatch[0].end, 'som'.length); // Basename Camelcase - const basenameCamelcaseRes = scorer.scoreItem(resource, 'sF', true, ResourceAccessor, cache); + const basenameCamelcaseRes = scoreItem(resource, 'sF', true, ResourceAccessor, cache); assert.ok(basenameCamelcaseRes.score); assert.ok(!basenameCamelcaseRes.descriptionMatch); assert.equal(basenameCamelcaseRes.labelMatch.length, 2); @@ -145,7 +148,7 @@ suite('Quick Open Scorer', () => { assert.equal(basenameCamelcaseRes.labelMatch[1].end, 5); // Basename Match - const basenameRes = scorer.scoreItem(resource, 'of', true, ResourceAccessor, cache); + const basenameRes = scoreItem(resource, 'of', true, ResourceAccessor, cache); assert.ok(basenameRes.score); assert.ok(!basenameRes.descriptionMatch); assert.equal(basenameRes.labelMatch.length, 2); @@ -155,7 +158,7 @@ suite('Quick Open Scorer', () => { assert.equal(basenameRes.labelMatch[1].end, 5); // Path Match - const pathRes = scorer.scoreItem(resource, 'xyz123', true, ResourceAccessor, cache); + const pathRes = scoreItem(resource, 'xyz123', true, ResourceAccessor, cache); assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); @@ -167,7 +170,7 @@ suite('Quick Open Scorer', () => { assert.equal(pathRes.descriptionMatch[0].end, 4); // No Match - const noRes = scorer.scoreItem(resource, '987', true, ResourceAccessor, cache); + const noRes = scoreItem(resource, '987', true, ResourceAccessor, cache); assert.ok(!noRes.score); assert.ok(!noRes.labelMatch); assert.ok(!noRes.descriptionMatch); @@ -179,13 +182,22 @@ suite('Quick Open Scorer', () => { assert.ok(pathRes.score > noRes.score); }); + test('scoreItem - invalid input', function () { + + let res = scoreItem(null, null, true, ResourceAccessor, cache); + assert.equal(res.score, 0); + + res = scoreItem(null, 'null', true, ResourceAccessor, cache); + assert.equal(res.score, 0); + }); + test('scoreItem - optimize for file paths', function () { const resource = URI.file('/xyz/others/spath/some/xsp/file123.txt'); // xsp is more relevant to the end of the file path even though it matches // fuzzy also in the beginning. we verify the more relevant match at the // end gets returned. - const pathRes = scorer.scoreItem(resource, 'xspfile123', true, ResourceAccessor, cache); + const pathRes = scoreItem(resource, 'xspfile123', true, ResourceAccessor, cache); assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); @@ -197,6 +209,22 @@ suite('Quick Open Scorer', () => { assert.equal(pathRes.descriptionMatch[0].end, 26); }); + test('scoreItem - prefers more compact matches', function () { + const resource = URI.file('/1a111d1/11a1d1/something.txt'); + + // expect "ad" to be matched towards the end of the file because the + // match is more compact + const res = scoreItem(resource, 'ad', true, ResourceAccessor, cache); + assert.ok(res.score); + assert.ok(res.descriptionMatch); + assert.ok(!res.labelMatch.length); + assert.equal(res.descriptionMatch.length, 2); + assert.equal(res.descriptionMatch[0].start, 11); + assert.equal(res.descriptionMatch[0].end, 12); + assert.equal(res.descriptionMatch[1].start, 13); + assert.equal(res.descriptionMatch[1].end, 14); + }); + test('compareItemsByScore - identity', function () { const resourceA = URI.file('/some/path/fileA.txt'); const resourceB = URI.file('/some/path/other/fileB.txt'); @@ -205,12 +233,12 @@ suite('Quick Open Scorer', () => { // Full resource A path let query = ResourceAccessor.getItemPath(resourceA); - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); @@ -218,12 +246,12 @@ suite('Quick Open Scorer', () => { // Full resource B path query = ResourceAccessor.getItemPath(resourceB); - res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); @@ -237,12 +265,12 @@ suite('Quick Open Scorer', () => { // Full resource A basename let query = ResourceAccessor.getItemLabel(resourceA); - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); @@ -250,12 +278,12 @@ suite('Quick Open Scorer', () => { // Full resource B basename query = ResourceAccessor.getItemLabel(resourceB); - res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); @@ -269,12 +297,12 @@ suite('Quick Open Scorer', () => { // resource A camelcase let query = 'fA'; - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); @@ -282,12 +310,12 @@ suite('Quick Open Scorer', () => { // resource B camelcase query = 'fB'; - res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); @@ -301,12 +329,12 @@ suite('Quick Open Scorer', () => { // Resource A part of basename let query = 'fileA'; - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); @@ -314,12 +342,12 @@ suite('Quick Open Scorer', () => { // Resource B part of basename query = 'fileB'; - res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); @@ -333,12 +361,12 @@ suite('Quick Open Scorer', () => { // Resource A part of path let query = 'pathfileA'; - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); @@ -346,12 +374,12 @@ suite('Quick Open Scorer', () => { // Resource B part of path query = 'pathfileB'; - res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); @@ -365,17 +393,36 @@ suite('Quick Open Scorer', () => { // Resource A part of path let query = 'somepath'; - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); }); + test('compareFilesByScore - prefer shorter basenames (match on basename)', function () { + const resourceA = URI.file('/some/path/fileA.txt'); + const resourceB = URI.file('/some/path/other/fileBLonger.txt'); + const resourceC = URI.file('/unrelated/the/path/other/fileC.txt'); + + // Resource A part of path + let query = 'file'; + + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceA); + assert.equal(res[1], resourceC); + assert.equal(res[2], resourceB); + + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceA); + assert.equal(res[1], resourceC); + assert.equal(res[2], resourceB); + }); + test('compareFilesByScore - prefer shorter paths', function () { const resourceA = URI.file('/some/path/fileA.txt'); const resourceB = URI.file('/some/path/other/fileB.txt'); @@ -384,12 +431,12 @@ suite('Quick Open Scorer', () => { // Resource A part of path let query = 'somepath'; - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); - res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); assert.equal(res[2], resourceC); @@ -402,7 +449,7 @@ suite('Quick Open Scorer', () => { let query = 'co/te'; - let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); assert.equal(res[2], resourceC); @@ -414,12 +461,149 @@ suite('Quick Open Scorer', () => { let query = 'vscode'; - let res = [resourceA, resourceB].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => -1)); + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => -1)); assert.equal(res[0], resourceA); assert.equal(res[1], resourceB); - res = [resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => -1)); + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => -1)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); }); + + test('compareFilesByScore - prefer more compact camel case matches', function () { + const resourceA = URI.file('config/test/openthisAnythingHandler.js'); + const resourceB = URI.file('config/test/openthisisnotsorelevantforthequeryAnyHand.js'); + + let query = 'AH'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + }); + + test('compareFilesByScore - prefer more compact matches (label)', function () { + const resourceA = URI.file('config/test/examasdaple.js'); + const resourceB = URI.file('config/test/exampleasdaasd.ts'); + + let query = 'xp'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + }); + + test('compareFilesByScore - prefer more compact matches (path)', function () { + const resourceA = URI.file('config/test/examasdaple/file.js'); + const resourceB = URI.file('config/test/exampleasdaasd/file.ts'); + + let query = 'xp'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + }); + + test('compareFilesByScore - prefer more compact matches (label and path)', function () { + const resourceA = URI.file('config/example/thisfile.ts'); + const resourceB = URI.file('config/24234243244/example/file.js'); + + let query = 'exfile'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + }); + + test('compareFilesByScore - avoid match scattering (bug #34210)', function () { + const resourceA = URI.file('node_modules1/bundle/lib/model/modules/ot1/index.js'); + const resourceB = URI.file('node_modules1/bundle/lib/model/modules/un1/index.js'); + const resourceC = URI.file('node_modules1/bundle/lib/model/modules/modu1/index.js'); + const resourceD = URI.file('node_modules1/bundle/lib/model/modules/oddl1/index.js'); + + let query = isWindows ? 'modu1\\index.js' : 'modu1/index.js'; + + let res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceC); + + query = isWindows ? 'un1\\index.js' : 'un1/index.js'; + + res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + }); + + test('compareFilesByScore - avoid match scattering (bug #21019)', function () { + const resourceA = URI.file('app/containers/Services/NetworkData/ServiceDetails/ServiceLoad/index.js'); + const resourceB = URI.file('app/containers/Services/NetworkData/ServiceDetails/ServiceDistribution/index.js'); + const resourceC = URI.file('app/containers/Services/NetworkData/ServiceDetailTabs/ServiceTabs/StatVideo/index.js'); + + let query = 'StatVideoindex'; + + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceC); + }); + + test('compareFilesByScore - avoid match scattering (bug #26649)', function () { + const resourceA = URI.file('photobook/src/components/AddPagesButton/index.js'); + const resourceB = URI.file('photobook/src/components/ApprovalPageHeader/index.js'); + const resourceC = URI.file('photobook/src/canvasComponents/BookPage/index.js'); + + let query = 'bookpageIndex'; + + let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceC); + }); + + test('compareFilesByScore - avoid match scattering (bug #33247)', function () { + const resourceA = URI.file('ui/src/utils/constants.js'); + const resourceB = URI.file('ui/src/ui/Icons/index.js'); + + let query = isWindows ? 'ui\\icons' : 'ui/icons'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + }); + + test('compareFilesByScore - avoid match scattering (bug #33247 comment)', function () { + const resourceA = URI.file('ui/src/components/IDInput/index.js'); + const resourceB = URI.file('ui/src/ui/Input/index.js'); + + let query = isWindows ? 'ui\\input\\index' : 'ui/input/index'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + }); + + test('compareFilesByScore - prefer shorter hit (bug #20546)', function () { + const resourceA = URI.file('editor/core/components/tests/list-view-spec.js'); + const resourceB = URI.file('editor/core/components/list-view.js'); + + let query = 'listview'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); + assert.equal(res[0], resourceB); + }); + + test('prepareSearchForScoring', function () { + assert.equal(scorer.prepareQuery(' f*a ').value, 'fa'); + assert.equal(scorer.prepareQuery('model Tester.ts').value, 'modelTester.ts'); + assert.equal(scorer.prepareQuery('Model Tester.ts').lowercase, 'modeltester.ts'); + assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); + assert.equal(scorer.prepareQuery('Model' + nativeSep + 'Tester.ts').containsPathSeparator, true); + }); }); \ No newline at end of file diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index e53c60a028b..9eff83860da 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -319,8 +319,7 @@ suite('Map', () => { map.forEach((value, key) => { assert.equal(trie.get(key), value); }); - trie.forEach(entry => { - const [key, element] = entry; + trie.forEach((element, key) => { assert.equal(element, map.get(key)); map.delete(key); }); diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/node/glob.test.ts index 0e61295fdb8..301762e6130 100644 --- a/src/vs/base/test/node/glob.test.ts +++ b/src/vs/base/test/node/glob.test.ts @@ -933,4 +933,8 @@ suite('Glob', () => { assert(!glob.match(p, '/DNXConsoleApp/foo/Program.cs')); } }); + + test('pattern with "base" does not explode - #36081', function () { + assert.ok(glob.match({ 'base': true }, 'base')); + }); }); \ No newline at end of file diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 7b1efdfba4f..b2af39492b6 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -76,7 +76,7 @@ export class CodeApplication { @ILogService private logService: ILogService, @IEnvironmentService private environmentService: IEnvironmentService, @ILifecycleService private lifecycleService: ILifecycleService, - @IConfigurationService private configurationService: ConfigurationService, + @IConfigurationService private configurationService: ConfigurationService, @IStorageService private storageService: IStorageService, @IHistoryMainService private historyService: IHistoryMainService ) { diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 0accad12c78..df337ac6675 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -11,8 +11,8 @@ 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, BrowserWindow } from 'electron'; import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration, AutoSaveConfiguration } from 'vs/platform/files/common/files'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { AutoSaveConfiguration } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update'; import product from 'vs/platform/node/product'; @@ -29,27 +29,6 @@ interface IExtensionViewlet { label: string; } -interface IConfiguration extends IFilesConfiguration { - window: { - enableMenuBarMnemonics: boolean; - nativeTabs: boolean; - }; - workbench: { - sideBar: { - location: 'left' | 'right'; - }, - statusBar: { - visible: boolean; - }, - activityBar: { - visible: boolean; - } - }; - editor: { - multiCursorModifier: 'ctrlCmd' | 'alt' - }; -} - interface IMenuItemClickHandler { inDevTools: (contents: Electron.WebContents) => void; inNoWindow: () => void; @@ -61,13 +40,15 @@ export class CodeMenu { private static MAX_MENU_RECENT_ENTRIES = 10; - private currentAutoSaveSetting: string; - private currentMultiCursorModifierSetting: string; - private currentSidebarLocation: 'left' | 'right'; - private currentStatusbarVisible: boolean; - private currentActivityBarVisible: boolean; - private currentEnableMenuBarMnemonics: boolean; - private currentEnableNativeTabs: boolean; + private keys = [ + 'files.autoSave', + 'editor.multiCursorModifier', + 'workbench.sideBar.location', + 'workbench.statusBar.visible', + 'workbench.activityBar.visible', + 'window.enableMenuBarMnemonics', + 'window.nativeTabs' + ]; private isQuitting: boolean; private appMenuInstalled: boolean; @@ -99,8 +80,6 @@ export class CodeMenu { this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); this.keybindingsResolver = instantiationService.createInstance(KeybindingsResolver); - this.onConfigurationUpdated(this.configurationService.getConfiguration()); - this.install(); this.registerListeners(); @@ -136,7 +115,7 @@ export class CodeMenu { }); // Update when auto save config changes - this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration(), true /* update menu if changed */)); + this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)); // Listen to update service this.updateService.onStateChange(() => this.updateMenu()); @@ -145,67 +124,56 @@ export class CodeMenu { this.keybindingsResolver.onKeybindingsChanged(() => this.updateMenu()); } - private onConfigurationUpdated(config: IConfiguration, handleMenu?: boolean): void { - let updateMenu = false; - const newAutoSaveSetting = config && config.files && config.files.autoSave; - if (newAutoSaveSetting !== this.currentAutoSaveSetting) { - this.currentAutoSaveSetting = newAutoSaveSetting; - updateMenu = true; - } - - const newMultiCursorModifierSetting = config && config.editor && config.editor.multiCursorModifier; - if (newMultiCursorModifierSetting !== this.currentMultiCursorModifierSetting) { - this.currentMultiCursorModifierSetting = newMultiCursorModifierSetting; - updateMenu = true; - } - - const newSidebarLocation = config && config.workbench && config.workbench.sideBar && config.workbench.sideBar.location || 'left'; - if (newSidebarLocation !== this.currentSidebarLocation) { - this.currentSidebarLocation = newSidebarLocation; - updateMenu = true; - } - - let newStatusbarVisible = config && config.workbench && config.workbench.statusBar && config.workbench.statusBar.visible; - if (typeof newStatusbarVisible !== 'boolean') { - newStatusbarVisible = true; - } - if (newStatusbarVisible !== this.currentStatusbarVisible) { - this.currentStatusbarVisible = newStatusbarVisible; - updateMenu = true; - } - - let newActivityBarVisible = config && config.workbench && config.workbench.activityBar && config.workbench.activityBar.visible; - if (typeof newActivityBarVisible !== 'boolean') { - newActivityBarVisible = true; - } - if (newActivityBarVisible !== this.currentActivityBarVisible) { - this.currentActivityBarVisible = newActivityBarVisible; - updateMenu = true; - } - - let newEnableMenuBarMnemonics = config && config.window && config.window.enableMenuBarMnemonics; - if (typeof newEnableMenuBarMnemonics !== 'boolean') { - newEnableMenuBarMnemonics = true; - } - if (newEnableMenuBarMnemonics !== this.currentEnableMenuBarMnemonics) { - this.currentEnableMenuBarMnemonics = newEnableMenuBarMnemonics; - updateMenu = true; - } - - let newEnableNativeTabs = config && config.window && config.window.nativeTabs; - if (typeof newEnableNativeTabs !== 'boolean') { - newEnableNativeTabs = false; - } - if (newEnableNativeTabs !== this.currentEnableNativeTabs) { - this.currentEnableNativeTabs = newEnableNativeTabs; - updateMenu = true; - } - - if (handleMenu && updateMenu) { + private onConfigurationUpdated(event: IConfigurationChangeEvent): void { + if (this.keys.some(key => event.affectsConfiguration(key))) { this.updateMenu(); } } + private get currentAutoSaveSetting(): string { + return this.configurationService.getValue('files.autoSave'); + } + + private get currentMultiCursorModifierSetting(): string { + return this.configurationService.getValue('editor.multiCursorModifier'); + } + + private get currentSidebarLocation(): string { + return this.configurationService.getValue('workbench.sideBar.location') || 'left'; + } + + private get currentStatusbarVisible(): boolean { + let statusbarVisible = this.configurationService.getValue('workbench.statusBar.visible'); + if (typeof statusbarVisible !== 'boolean') { + statusbarVisible = true; + } + return statusbarVisible; + } + + private get currentActivityBarVisible(): boolean { + let activityBarVisible = this.configurationService.getValue('workbench.activityBar.visible'); + if (typeof activityBarVisible !== 'boolean') { + activityBarVisible = true; + } + return activityBarVisible; + } + + private get currentEnableMenuBarMnemonics(): boolean { + let enableMenuBarMnemonics = this.configurationService.getValue('window.enableMenuBarMnemonics'); + if (typeof enableMenuBarMnemonics !== 'boolean') { + enableMenuBarMnemonics = true; + } + return enableMenuBarMnemonics; + } + + private get currentEnableNativeTabs(): boolean { + let enableNativeTabs = this.configurationService.getValue('window.nativeTabs'); + if (typeof enableNativeTabs !== 'boolean') { + enableNativeTabs = false; + } + return enableNativeTabs; + } + private updateMenu(): void { this.menuUpdater.schedule(); // buffer multiple attempts to update the menu } @@ -403,8 +371,6 @@ export class CodeMenu { this.setOpenRecentMenu(openRecentMenu); const openRecent = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu, enabled: openRecentMenu.items.length > 0 }); - const isMultiRootEnabled = (product.quality !== 'stable'); // TODO@Ben multi root - const saveWorkspaceAs = this.createMenuItem(nls.localize({ key: 'miSaveWorkspaceAs', comment: ['&& denotes a mnemonic'] }, "&&Save Workspace As..."), 'workbench.action.saveWorkspaceAs'); const addFolder = this.createMenuItem(nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Add Folder to Workspace..."), 'workbench.action.addRootFolder'); @@ -437,11 +403,11 @@ export class CodeMenu { isMacintosh ? open : null, !isMacintosh ? openFile : null, !isMacintosh ? openFolder : null, - isMultiRootEnabled ? openWorkspace : null, + openWorkspace, openRecent, - isMultiRootEnabled ? __separator__() : null, - isMultiRootEnabled ? addFolder : null, - isMultiRootEnabled ? saveWorkspaceAs : null, + __separator__(), + addFolder, + saveWorkspaceAs, __separator__(), saveFile, saveFileAs, diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 50570fd57b9..e1999859678 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -409,7 +409,7 @@ export class CodeWindow implements ICodeWindow { } // Handle configuration changes - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated())); // Handle Workspace events this.toDispose.push(this.workspaceService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e))); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 1262489e565..0698760579e 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -362,7 +362,7 @@ export class WindowsManager implements IWindowsMainService { // When run with --add, take the folders that are to be opened as // folders that should be added to the currently active window. let foldersToAdd: IPath[] = []; - if (openConfig.addMode && product.quality !== 'stable') { // TODO@Ben multi root + if (openConfig.addMode) { foldersToAdd = pathsToOpen.filter(path => !!path.folderPath).map(path => ({ filePath: path.folderPath })); pathsToOpen = pathsToOpen.filter(path => !path.folderPath); } @@ -792,7 +792,7 @@ export class WindowsManager implements IWindowsMainService { // This will ensure to open these folders in one window instead of multiple // If we are in addMode, we should not do this because in that case all // folders should be added to the existing window. - if (!openConfig.addMode && isCommandLineOrAPICall && product.quality !== 'stable') { // TODO@Ben multi root + if (!openConfig.addMode && isCommandLineOrAPICall) { const foldersToOpen = windowsToOpen.filter(path => !!path.folderPath); if (foldersToOpen.length > 1) { const workspace = this.workspacesService.createWorkspaceSync(foldersToOpen.map(folder => folder.folderPath)); @@ -937,7 +937,7 @@ export class WindowsManager implements IWindowsMainService { restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting; if (restoreWindows === 'one' /* default */ && windowConfig && windowConfig.reopenFolders) { - restoreWindows = windowConfig.reopenFolders; // TODO@Ben migration + restoreWindows = windowConfig.reopenFolders; // TODO@Ben migration from deprecated window.reopenFolders setting } if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) { diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 185f3537c51..050937ddb51 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -11,8 +11,11 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditor } from 'vs/editor/browser/codeEditor'; -import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IConfigurationChangedEvent, IEditorOptions, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { IMessageService } from 'vs/platform/message/common/message'; export class EmbeddedCodeEditorWidget extends CodeEditor { @@ -40,7 +43,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditor { this._register(parentEditor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => this._onParentConfigurationChanged(e))); } - public getParentEditor(): ICodeEditor { + getParentEditor(): ICodeEditor { return this._parentEditor; } @@ -49,7 +52,49 @@ export class EmbeddedCodeEditorWidget extends CodeEditor { super.updateOptions(this._overwriteOptions); } - public updateOptions(newOptions: IEditorOptions): void { + updateOptions(newOptions: IEditorOptions): void { + objects.mixin(this._overwriteOptions, newOptions, true); + super.updateOptions(this._overwriteOptions); + } +} + +export class EmbeddedDiffEditorWidget extends DiffEditorWidget { + + private _parentEditor: ICodeEditor; + private _overwriteOptions: IDiffEditorOptions; + + constructor( + domElement: HTMLElement, + options: IDiffEditorOptions, + parentEditor: ICodeEditor, + @IEditorWorkerService editorWorkerService: IEditorWorkerService, + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService codeEditorService: ICodeEditorService, + @IThemeService themeService: IThemeService, + @IMessageService messageService: IMessageService + ) { + super(domElement, parentEditor.getRawConfiguration(), editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, messageService); + + this._parentEditor = parentEditor; + this._overwriteOptions = options; + + // Overwrite parent's options + super.updateOptions(this._overwriteOptions); + + this._register(parentEditor.onDidChangeConfiguration(e => this._onParentConfigurationChanged(e))); + } + + getParentEditor(): ICodeEditor { + return this._parentEditor; + } + + private _onParentConfigurationChanged(e: IConfigurationChangedEvent): void { + super.updateOptions(this._parentEditor.getRawConfiguration()); + super.updateOptions(this._overwriteOptions); + } + + updateOptions(newOptions: IEditorOptions): void { objects.mixin(this._overwriteOptions, newOptions, true); super.updateOptions(this._overwriteOptions); } diff --git a/src/vs/editor/common/editorCommonExtensions.ts b/src/vs/editor/common/editorCommonExtensions.ts index fee9cdfbd3a..aaea4371983 100644 --- a/src/vs/editor/common/editorCommonExtensions.ts +++ b/src/vs/editor/common/editorCommonExtensions.ts @@ -153,6 +153,7 @@ export abstract class EditorCommand extends Command { export interface IEditorCommandMenuOptions { group?: string; order?: number; + when?: ContextKeyExpr; } export interface IActionOptions extends ICommandOptions { label: string; @@ -182,7 +183,7 @@ export abstract class EditorAction extends EditorCommand { id: this.id, title: this.label }, - when: this.precondition, + when: ContextKeyExpr.and(this.precondition, this.menuOpts.when), group: this.menuOpts.group, order: this.menuOpts.order }; diff --git a/src/vs/editor/common/model/indentRanges.ts b/src/vs/editor/common/model/indentRanges.ts index 3a7773f592e..21a5311146b 100644 --- a/src/vs/editor/common/model/indentRanges.ts +++ b/src/vs/editor/common/model/indentRanges.ts @@ -61,41 +61,47 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol // folding pattern match if (m[1]) { // start pattern match // discard all regions until the folding pattern - while (previous.indent >= 0 && !previous.marker) { - previousRegions.pop(); - previous = previousRegions[previousRegions.length - 1]; + let i = previousRegions.length - 1; + while (i > 0 && !previousRegions[i].marker) { + i--; } - if (previous.marker) { + if (i > 0) { + previousRegions.length = i + 1; + previous = previousRegions[i]; + // new folding range from pattern, includes the end line result.push(new IndentRange(line, previous.line, indent, true)); previous.marker = false; previous.indent = indent; previous.line = line; + continue; + } else { + // no end marker found, treat line as a regular line } } else { // end pattern match previousRegions.push({ indent: -2, line, marker: true }); + continue; } - } else { - if (previous.indent > indent) { - // discard all regions with larger indent - do { - previousRegions.pop(); - previous = previousRegions[previousRegions.length - 1]; - } while (previous.indent > indent); + } + if (previous.indent > indent) { + // discard all regions with larger indent + do { + previousRegions.pop(); + previous = previousRegions[previousRegions.length - 1]; + } while (previous.indent > indent); - // new folding range - let endLineNumber = previous.line - 1; - if (endLineNumber - line >= minimumRangeSize) { - result.push(new IndentRange(line, endLineNumber, indent)); - } - } - if (previous.indent === indent) { - previous.line = line; - } else { // previous.indent < indent - // new region with a bigger indent - previousRegions.push({ indent, line, marker: false }); + // new folding range + let endLineNumber = previous.line - 1; + if (endLineNumber - line >= minimumRangeSize) { + result.push(new IndentRange(line, endLineNumber, indent)); } } + if (previous.indent === indent) { + previous.line = line; + } else { // previous.indent < indent + // new region with a bigger indent + previousRegions.push({ indent, line, marker: false }); + } } return result.reverse(); diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 7eb325a6761..1fbc491f271 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -224,7 +224,7 @@ export class ModelServiceImpl implements IModelService { this._markerServiceSubscription = this._markerService.onMarkerChanged(this._handleMarkerChange, this); } - this._configurationServiceSubscription = this._configurationService.onDidUpdateConfiguration(e => this._updateModelOptions()); + this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()); this._updateModelOptions(); } @@ -272,7 +272,7 @@ export class ModelServiceImpl implements IModelService { public getCreationOptions(language: string, resource: URI): editorCommon.ITextModelCreationOptions { let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource]; if (!creationOptions) { - creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getConfiguration(null, { overrideIdentifier: language, resource })); + creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getConfiguration({ overrideIdentifier: language, resource })); this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions; } return creationOptions; diff --git a/src/vs/editor/common/services/resourceConfiguration.ts b/src/vs/editor/common/services/resourceConfiguration.ts index 2a959539e9b..e65c4a12104 100644 --- a/src/vs/editor/common/services/resourceConfiguration.ts +++ b/src/vs/editor/common/services/resourceConfiguration.ts @@ -7,6 +7,7 @@ import Event from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IPosition } from 'vs/editor/common/core/position'; +import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; export const ITextResourceConfigurationService = createDecorator('textResourceConfigurationService'); @@ -17,7 +18,7 @@ export interface ITextResourceConfigurationService { /** * Event that fires when the configuration changes. */ - onDidUpdateConfiguration: Event; + onDidChangeConfiguration: Event; /** * Fetches the appropriate section of the for the given resource with appropriate overrides (e.g. language). diff --git a/src/vs/editor/common/services/resourceConfigurationImpl.ts b/src/vs/editor/common/services/resourceConfigurationImpl.ts index 453395b6488..6124db46a06 100644 --- a/src/vs/editor/common/services/resourceConfigurationImpl.ts +++ b/src/vs/editor/common/services/resourceConfigurationImpl.ts @@ -6,7 +6,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -16,8 +16,8 @@ export class TextResourceConfigurationService extends Disposable implements ITex public _serviceBrand: any; - private readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; constructor( @IConfigurationService private configurationService: IConfigurationService, @@ -25,7 +25,7 @@ export class TextResourceConfigurationService extends Disposable implements ITex @IModeService private modeService: IModeService, ) { super(); - this._register(this.configurationService.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire())); + this._register(this.configurationService.onDidChangeConfiguration(e => this._onDidChangeConfiguration.fire(e))); } getConfiguration(resource: URI, section?: string): T diff --git a/src/vs/editor/contrib/find/browser/simpleFindWidget.css b/src/vs/editor/contrib/find/browser/simpleFindWidget.css index 28987124e4b..526b08a05b1 100644 --- a/src/vs/editor/contrib/find/browser/simpleFindWidget.css +++ b/src/vs/editor/contrib/find/browser/simpleFindWidget.css @@ -11,6 +11,7 @@ right: 28px; width: 220px; max-width: calc(100% - 28px - 28px - 8px); + pointer-events: none; } .monaco-workbench .simple-find-part { @@ -20,6 +21,7 @@ display: flex; padding: 4px; align-items: center; + pointer-events: all; -webkit-transition: top 200ms linear; -o-transition: top 200ms linear; diff --git a/src/vs/editor/contrib/format/browser/formatActions.ts b/src/vs/editor/contrib/format/browser/formatActions.ts index de620bc090b..62cd203a97b 100644 --- a/src/vs/editor/contrib/format/browser/formatActions.ts +++ b/src/vs/editor/contrib/format/browser/formatActions.ts @@ -13,7 +13,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { editorAction, ServicesAccessor, EditorAction, commonEditorContribution } from 'vs/editor/common/editorCommonExtensions'; import { OnTypeFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes'; -import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits } from '../common/format'; +import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits, NoProviderError } from '../common/format'; import { EditOperationsCommand } from '../common/formatCommand'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; @@ -23,6 +23,7 @@ import { Range } from 'vs/editor/common/core/range'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { EditorState, CodeEditorStateFlag } from 'vs/editor/common/core/editorState'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { IMessageService, Severity } from 'vs/platform/message/common/message'; function alertFormattingEdits(edits: editorCommon.ISingleEditOperation[]): void { @@ -263,6 +264,7 @@ export abstract class AbstractFormatAction extends EditorAction { public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): TPromise { const workerService = accessor.get(IEditorWorkerService); + const messageService = accessor.get(IMessageService); const formattingPromise = this._getFormattingEdits(editor); if (!formattingPromise) { @@ -281,6 +283,15 @@ export abstract class AbstractFormatAction extends EditorAction { EditOperationsCommand.execute(editor, edits); alertFormattingEdits(edits); editor.focus(); + }, err => { + if (err instanceof Error && err.name === NoProviderError.Name) { + messageService.show( + Severity.Info, + nls.localize('no.provider', "Sorry, but there is no formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language), + ); + } else { + throw err; + } }); } @@ -296,7 +307,7 @@ export class FormatDocumentAction extends AbstractFormatAction { id: 'editor.action.formatDocument', label: nls.localize('formatDocument.label', "Format Document"), alias: 'Format Document', - precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider), + precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.textFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F, @@ -304,6 +315,7 @@ export class FormatDocumentAction extends AbstractFormatAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I } }, menuOpts: { + when: EditorContextKeys.hasDocumentFormattingProvider, group: '1_modification', order: 1.3 } @@ -325,12 +337,13 @@ export class FormatSelectionAction extends AbstractFormatAction { id: 'editor.action.formatSelection', label: nls.localize('formatSelection.label', "Format Selection"), alias: 'Format Code', - precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection), + precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasNonEmptySelection), kbOpts: { kbExpr: EditorContextKeys.textFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_F) }, menuOpts: { + when: ContextKeyExpr.and(EditorContextKeys.hasDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection), group: '1_modification', order: 1.31 } diff --git a/src/vs/editor/contrib/format/common/format.ts b/src/vs/editor/contrib/format/common/format.ts index 0f8bb567719..37c0f0246fd 100644 --- a/src/vs/editor/contrib/format/common/format.ts +++ b/src/vs/editor/contrib/format/common/format.ts @@ -17,12 +17,23 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { asWinJsPromise, sequence } from 'vs/base/common/async'; import { Position } from 'vs/editor/common/core/position'; -export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Range, options: FormattingOptions): TPromise { +export class NoProviderError extends Error { + + static readonly Name = 'NOPRO'; + + constructor(message?: string) { + super(); + this.name = NoProviderError.Name; + this.message = message; + } +} + +export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Range, options: FormattingOptions): TPromise { const providers = DocumentRangeFormattingEditProviderRegistry.ordered(model); if (providers.length === 0) { - return TPromise.as(undefined); + return TPromise.wrapError(new NoProviderError()); } let result: TextEdit[]; diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclarationCommands.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclarationCommands.ts index 0cede0a44db..5db1dae02f9 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclarationCommands.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclarationCommands.ts @@ -22,7 +22,7 @@ import { ReferencesController } from 'vs/editor/contrib/referenceSearch/browser/ import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/browser/referencesModel'; import { PeekContext } from 'vs/editor/contrib/referenceSearch/browser/peekViewWidget'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { MessageController } from './messageController'; +import { MessageController } from 'vs/editor/contrib/message/messageController'; import * as corePosition from 'vs/editor/common/core/position'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IProgressService } from 'vs/platform/progress/common/progress'; diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index c973826bfa7..179eb9d2973 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -219,5 +219,4 @@ registerThemingParticipant((theme, collector) => { if (codeBackground) { collector.addRule(`.monaco-editor .monaco-editor-hover code { background-color: ${codeBackground}; }`); } - }); diff --git a/src/vs/editor/contrib/goToDeclaration/browser/messageController.css b/src/vs/editor/contrib/message/messageController.css similarity index 100% rename from src/vs/editor/contrib/goToDeclaration/browser/messageController.css rename to src/vs/editor/contrib/message/messageController.css diff --git a/src/vs/editor/contrib/goToDeclaration/browser/messageController.ts b/src/vs/editor/contrib/message/messageController.ts similarity index 98% rename from src/vs/editor/contrib/goToDeclaration/browser/messageController.ts rename to src/vs/editor/contrib/message/messageController.ts index 693722e4c52..ce5d45af58c 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -47,10 +47,14 @@ export class MessageController { this._visible = MessageController.CONTEXT_SNIPPET_MODE.bindTo(contextKeyService); } - dispose() { + dispose(): void { this._visible.reset(); } + isVisible() { + return this._visible.get(); + } + showMessage(message: string, position: IPosition): void { alert(message); diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHints.css b/src/vs/editor/contrib/parameterHints/browser/parameterHints.css index 812bff41e87..2f89fb1658c 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHints.css @@ -56,6 +56,11 @@ white-space: pre-wrap; } +.monaco-editor .parameter-hints-widget .docs code { + border-radius: 3px; + padding: 0 0.4em; +} + .monaco-editor .parameter-hints-widget .buttons { position: absolute; display: none; diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index fd7fe899f0d..2d022f949a3 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -25,7 +25,7 @@ import { CharacterSet } from 'vs/editor/common/core/characterClassifier'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { registerThemingParticipant, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { editorHoverBackground, editorHoverBorder, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IModeService } from 'vs/editor/common/services/modeService'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/browser/markdownRenderer'; @@ -511,4 +511,9 @@ registerThemingParticipant((theme, collector) => { if (link) { collector.addRule(`.monaco-editor .parameter-hints-widget a { color: ${link}; }`); } + + let codeBackground = theme.getColor(textCodeBlockBackground); + if (codeBackground) { + collector.addRule(`.monaco-editor .parameter-hints-widget code { background-color: ${codeBackground}; }`); + } }); diff --git a/src/vs/editor/contrib/referenceSearch/browser/media/peekViewWidget.css b/src/vs/editor/contrib/referenceSearch/browser/media/peekViewWidget.css index 8ef899a7815..3e253ab57b7 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/media/peekViewWidget.css +++ b/src/vs/editor/contrib/referenceSearch/browser/media/peekViewWidget.css @@ -9,6 +9,7 @@ -moz-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box; + display: flex; } .monaco-editor .peekview-widget .head .peekview-title { @@ -24,16 +25,35 @@ } .monaco-editor .peekview-widget .head .peekview-actions { - display: inline-block; - position: absolute; - right: 2px; - top: 2px; + flex: 1; + text-align: right; + padding-right: 2px; } -.monaco-editor .peekview-widget .head .peekview-actions .action-label { +.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar { + display: inline-block; +} + +.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar, +.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar > .actions-container { + height: 100%; +} + +.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-item { + margin-left: 4px; +} + +.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label { width: 16px; - height: 16px; - margin: 2px 0; + height: 100%; + margin: 0; + line-height: inherit; + background-repeat: no-repeat; + background-position: center center; +} + +.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label.octicon { + margin: 0; } .monaco-editor .peekview-widget .head .peekview-actions .action-label.icon.close-peekview-action { diff --git a/src/vs/editor/contrib/referenceSearch/browser/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/browser/peekViewWidget.ts index a0a78bac128..09aab8dcdcd 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/peekViewWidget.ts @@ -13,7 +13,7 @@ import * as objects from 'vs/base/common/objects'; import { $ } from 'vs/base/browser/builder'; import Event, { Emitter } from 'vs/base/common/event'; import * as dom from 'vs/base/browser/dom'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; @@ -132,10 +132,9 @@ export abstract class PeekViewWidget extends ZoneWidget { this._secondaryHeading = $('span.dirname').appendTo(titleElement).getHTMLElement(); this._metaHeading = $('span.meta').appendTo(titleElement).getHTMLElement(); - this._actionbarWidget = new ActionBar( - $('.peekview-actions'). - appendTo(this._headElement) - ); + const actionsContainer = $('.peekview-actions').appendTo(this._headElement); + const actionBarOptions = this._getActionBarOptions(); + this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions); this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), 'close-peekview-action', true, () => { this.dispose(); @@ -143,6 +142,10 @@ export abstract class PeekViewWidget extends ZoneWidget { }), { label: false, icon: true }); } + protected _getActionBarOptions(): IActionBarOptions { + return {}; + } + protected _onTitleClick(event: MouseEvent): void { // implement me } @@ -165,9 +168,7 @@ export abstract class PeekViewWidget extends ZoneWidget { } } - protected _fillBody(container: HTMLElement): void { - // implement me - } + protected abstract _fillBody(container: HTMLElement): void; public _doLayout(heightInPixel: number, widthInPixel: number): void { diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts index d268a392aff..7678766238c 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts @@ -123,7 +123,7 @@ export class ReferencesController implements editorCommon.IEditorContribution { switch (kind) { case 'open': if (event.source === 'editor' - && this._configurationService.lookup('editor.stablePeek').value) { + && this._configurationService.getValue('editor.stablePeek')) { // when stable peek is configured we don't close // the peek window on selecting the editor diff --git a/src/vs/editor/contrib/snippet/browser/snippet.md b/src/vs/editor/contrib/snippet/browser/snippet.md index df77dd8f821..458cccbdb96 100644 --- a/src/vs/editor/contrib/snippet/browser/snippet.md +++ b/src/vs/editor/contrib/snippet/browser/snippet.md @@ -34,7 +34,7 @@ Variable-Transform Transformations allow to modify the value of a variable before it is being inserted. The definition of a transformation consists of three parts: 1. A regular expression that is matched against the value of a variable, or the empty string when the variable cannot be resolved. -2. A "format string" that allows to reference matching groups from the regular expression. The format string also for conditional inserts and simple modifications. +2. A "format string" that allows to reference matching groups from the regular expression. The format string allows for conditional inserts and simple modifications. 3. Options that are passed to the regular expression The following sample inserts the name of the current file without its ending, so from `foo.txt` it makes `foo`. @@ -71,7 +71,7 @@ format ::= '$' int | '${' int '}' | '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}' | '${' int ':+' if '}' | '${' int ':?' if ':' else '}' - | '${' int ':-' else '}' '${' int ':' else '}' + | '${' int ':-' else '}' | '${' int ':' else '}' regex ::= JavaScript Regular Expression value (ctor-string) options ::= JavaScript Regular Expression option (ctor-options) var ::= [_a-zA-Z] [_a-zA-Z0-9]* diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts index e5de27daa03..aad0d69ba7a 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts @@ -164,4 +164,12 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve2('${foobarfoobar/(foo)/${2:+FAR}/g}', 'barbar'); // bad group reference }); + + // test('Snippet transforms do not handle regex with alternatives or optional matches, #36089', function () { + // assertVariableResolve2( + // '${TM_FILENAME/^(.)|(?:-(.))|(\\.js)/${1:+/upcase}${2:+/upcase}/g}', + // 'MyClass', + // 'my-class.js' + // ); + // }); }); diff --git a/src/vs/editor/contrib/suggest/browser/media/suggest.css b/src/vs/editor/contrib/suggest/browser/media/suggest.css index 2cd4a1b4896..f51c70d511a 100644 --- a/src/vs/editor/contrib/suggest/browser/media/suggest.css +++ b/src/vs/editor/contrib/suggest/browser/media/suggest.css @@ -244,6 +244,10 @@ display: none; } +.monaco-editor .suggest-widget .details code { + border-radius: 3px; + padding: 0 0.4em; +} /* High Contrast and Dark Theming */ diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index ebd0216c50a..f7a076a2780 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -28,7 +28,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachListStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/browser/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -1117,4 +1117,9 @@ registerThemingParticipant((theme, collector) => { if (link) { collector.addRule(`.monaco-editor .suggest-widget a { color: ${link}; }`); } + + let codeBackground = theme.getColor(textCodeBlockBackground); + if (codeBackground) { + collector.addRule(`.monaco-editor .suggest-widget code { background-color: ${codeBackground}; }`); + } }); diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index f2ebd600999..da7e44b07dd 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -17,7 +17,7 @@ import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; import { IdGenerator } from 'vs/base/common/idGenerator'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { ScrollType, TrackedRangeStickiness } from 'vs/editor/common/editorCommon'; export interface IOptions { showFrame?: boolean; @@ -147,7 +147,7 @@ class Arrow { show(where: IPosition): void { this._decorations = this._editor.deltaDecorations( this._decorations, - [{ range: Range.fromPositions(where), options: { className: this._ruleName } }] + [{ range: Range.fromPositions(where), options: { className: this._ruleName, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }] ); } @@ -395,7 +395,11 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { // Reveal the line above or below the zone widget, to get the zone widget in the viewport const revealLineNumber = Math.min(this.editor.getModel().getLineCount(), Math.max(1, where.endLineNumber + 1)); - this.editor.revealLine(revealLineNumber, ScrollType.Smooth); + this.revealLine(revealLineNumber); + } + + protected revealLine(lineNumber: number) { + this.editor.revealLine(lineNumber, ScrollType.Smooth); } protected setCssClass(className: string, classToReplace?: string): void { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 98464fac20a..88a323b05be 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -8,7 +8,7 @@ import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, IConfigurationKeys, IConfigurationValues, Configuration, IConfigurationData, ConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IEditor, IEditorInput, IEditorOptions, IEditorService, IResourceInput, Position } from 'vs/platform/editor/common/editor'; import { ICommandService, ICommand, ICommandEvent, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -23,7 +23,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; import Event, { Emitter } from 'vs/base/common/event'; -import { DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; +import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; @@ -434,45 +434,68 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { } } +function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { + return thing + && typeof thing === 'object' + && (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string') + && (!thing.resource || thing.resource instanceof URI); +} + export class SimpleConfigurationService implements IConfigurationService { _serviceBrand: any; - private _onDidUpdateConfiguration = new Emitter(); - public onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private _onDidChangeConfiguration = new Emitter(); + public onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - private _configuration: Configuration; + private _configuration: Configuration; constructor() { this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); } - private configuration(): Configuration { + private configuration(): Configuration { return this._configuration; } - public reloadConfiguration(section?: string): TPromise { - return TPromise.as(this.getConfiguration(section)); + getConfiguration(): T + getConfiguration(section: string): T + getConfiguration(overrides: IConfigurationOverrides): T + getConfiguration(section: string, overrides: IConfigurationOverrides): T + getConfiguration(arg1?: any, arg2?: any): any { + const section = typeof arg1 === 'string' ? arg1 : void 0; + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; + return this.configuration().getSection(section, overrides); } - public getConfiguration(section?: string, options?: IConfigurationOverrides): C { - return this.configuration().getValue(section, options); + public getValue(key: string, options?: IConfigurationOverrides): C { + return this.configuration().getValue(key, options); } - public lookup(key: string, options?: IConfigurationOverrides): IConfigurationValue { + public updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise { + return TPromise.as(null); + } + + public inspect(key: string, options?: IConfigurationOverrides): { + default: C, + user: C, + workspace: C, + workspaceFolder: C + value: C, + } { return this.configuration().lookup(key, options); } - public keys(): IConfigurationKeys { + public keys() { return this.configuration().keys(); } - public values(): IConfigurationValues { - return this._configuration.values(); + public reloadConfiguration(): TPromise { + return TPromise.as(null); } - public getConfigurationData(): IConfigurationData { - return this.configuration().toData(); + public getConfigurationData() { + return null; } } @@ -480,12 +503,12 @@ export class SimpleResourceConfigurationService implements ITextResourceConfigur _serviceBrand: any; - public readonly onDidUpdateConfiguration: Event; - private readonly _onDidUpdateConfigurationEmitter = new Emitter(); + public readonly onDidChangeConfiguration: Event; + private readonly _onDidChangeConfigurationEmitter = new Emitter(); constructor(private configurationService: SimpleConfigurationService) { - this.configurationService.onDidUpdateConfiguration(() => { - this._onDidUpdateConfigurationEmitter.fire(); + this.configurationService.onDidChangeConfiguration((e) => { + this._onDidChangeConfigurationEmitter.fire(e); }); } diff --git a/src/vs/editor/test/common/model/indentRanges.test.ts b/src/vs/editor/test/common/model/indentRanges.test.ts index 8d90c5a2635..6d09f3f1ce2 100644 --- a/src/vs/editor/test/common/model/indentRanges.test.ts +++ b/src/vs/editor/test/common/model/indentRanges.test.ts @@ -29,125 +29,125 @@ function r(startLineNumber: number, endLineNumber: number, indent: number, marke return { startLineNumber, endLineNumber, indent, marker }; } -// suite('Indentation Folding', () => { +suite('Indentation Folding', () => { -// test('Fold one level', () => { -// let range = [ -// 'A', -// ' A', -// ' A', -// ' A' -// ]; -// assertRanges(range, [r(1, 4, 0)], true); -// assertRanges(range, [r(1, 4, 0)], false); -// }); + test('Fold one level', () => { + let range = [ + 'A', + ' A', + ' A', + ' A' + ]; + assertRanges(range, [r(1, 4, 0)], true); + assertRanges(range, [r(1, 4, 0)], false); + }); -// test('Fold two levels', () => { -// let range = [ -// 'A', -// ' A', -// ' A', -// ' A', -// ' A' -// ]; -// assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], true); -// assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], false); -// }); + test('Fold two levels', () => { + let range = [ + 'A', + ' A', + ' A', + ' A', + ' A' + ]; + assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], true); + assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], false); + }); -// test('Fold three levels', () => { -// let range = [ -// 'A', -// ' A', -// ' A', -// ' A', -// 'A' -// ]; -// assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], true); -// assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], false); -// }); + test('Fold three levels', () => { + let range = [ + 'A', + ' A', + ' A', + ' A', + 'A' + ]; + assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], true); + assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], false); + }); -// test('Fold decreasing indent', () => { -// let range = [ -// ' A', -// ' A', -// 'A' -// ]; -// assertRanges(range, [], true); -// assertRanges(range, [], false); -// }); + test('Fold decreasing indent', () => { + let range = [ + ' A', + ' A', + 'A' + ]; + assertRanges(range, [], true); + assertRanges(range, [], false); + }); -// test('Fold Java', () => { -// assertRanges([ -// /* 1*/ 'class A {', -// /* 2*/ ' void foo() {', -// /* 3*/ ' console.log();', -// /* 4*/ ' console.log();', -// /* 5*/ ' }', -// /* 6*/ '', -// /* 7*/ ' void bar() {', -// /* 8*/ ' console.log();', -// /* 9*/ ' }', -// /*10*/ '}', -// /*11*/ 'interface B {', -// /*12*/ ' void bar();', -// /*13*/ '}', -// ], [r(1, 9, 0), r(2, 4, 2), r(7, 8, 2), r(11, 12, 0)], false); -// }); + test('Fold Java', () => { + assertRanges([ + /* 1*/ 'class A {', + /* 2*/ ' void foo() {', + /* 3*/ ' console.log();', + /* 4*/ ' console.log();', + /* 5*/ ' }', + /* 6*/ '', + /* 7*/ ' void bar() {', + /* 8*/ ' console.log();', + /* 9*/ ' }', + /*10*/ '}', + /*11*/ 'interface B {', + /*12*/ ' void bar();', + /*13*/ '}', + ], [r(1, 9, 0), r(2, 4, 2), r(7, 8, 2), r(11, 12, 0)], false); + }); -// test('Fold Javadoc', () => { -// assertRanges([ -// /* 1*/ '/**', -// /* 2*/ ' * Comment', -// /* 3*/ ' */', -// /* 4*/ 'class A {', -// /* 5*/ ' void foo() {', -// /* 6*/ ' }', -// /* 7*/ '}', -// ], [r(1, 3, 0), r(4, 6, 0)], false); -// }); -// test('Fold Whitespace Java', () => { -// assertRanges([ -// /* 1*/ 'class A {', -// /* 2*/ '', -// /* 3*/ ' void foo() {', -// /* 4*/ ' ', -// /* 5*/ ' return 0;', -// /* 6*/ ' }', -// /* 7*/ ' ', -// /* 8*/ '}', -// ], [r(1, 7, 0), r(3, 5, 2)], false); -// }); + test('Fold Javadoc', () => { + assertRanges([ + /* 1*/ '/**', + /* 2*/ ' * Comment', + /* 3*/ ' */', + /* 4*/ 'class A {', + /* 5*/ ' void foo() {', + /* 6*/ ' }', + /* 7*/ '}', + ], [r(1, 3, 0), r(4, 6, 0)], false); + }); + test('Fold Whitespace Java', () => { + assertRanges([ + /* 1*/ 'class A {', + /* 2*/ '', + /* 3*/ ' void foo() {', + /* 4*/ ' ', + /* 5*/ ' return 0;', + /* 6*/ ' }', + /* 7*/ ' ', + /* 8*/ '}', + ], [r(1, 7, 0), r(3, 5, 2)], false); + }); -// test('Fold Whitespace Python', () => { -// assertRanges([ -// /* 1*/ 'def a:', -// /* 2*/ ' pass', -// /* 3*/ ' ', -// /* 4*/ ' def b:', -// /* 5*/ ' pass', -// /* 6*/ ' ', -// /* 7*/ ' ', -// /* 8*/ 'def c: # since there was a deintent here' -// ], [r(1, 5, 0), r(4, 5, 2)], true); -// }); + test('Fold Whitespace Python', () => { + assertRanges([ + /* 1*/ 'def a:', + /* 2*/ ' pass', + /* 3*/ ' ', + /* 4*/ ' def b:', + /* 5*/ ' pass', + /* 6*/ ' ', + /* 7*/ ' ', + /* 8*/ 'def c: # since there was a deintent here' + ], [r(1, 5, 0), r(4, 5, 2)], true); + }); -// test('Fold Tabs', () => { -// assertRanges([ -// /* 1*/ 'class A {', -// /* 2*/ '\t\t', -// /* 3*/ '\tvoid foo() {', -// /* 4*/ '\t \t//hello', -// /* 5*/ '\t return 0;', -// /* 6*/ ' \t}', -// /* 7*/ ' ', -// /* 8*/ '}', -// ], [r(1, 7, 0), r(3, 5, 4)], false); -// }); -// }); + test('Fold Tabs', () => { + assertRanges([ + /* 1*/ 'class A {', + /* 2*/ '\t\t', + /* 3*/ '\tvoid foo() {', + /* 4*/ '\t \t//hello', + /* 5*/ '\t return 0;', + /* 6*/ ' \t}', + /* 7*/ ' ', + /* 8*/ '}', + ], [r(1, 7, 0), r(3, 5, 4)], false); + }); +}); let markers: FoldingMarkers = { - start: /^\s*#region/, - end: /^\s*#endregion/ + start: /^\s*#region\b/, + end: /^\s*#endregion\b/ }; suite('Folding with regions', () => { @@ -216,9 +216,9 @@ suite('Folding with regions', () => { /* 2*/ '#region', /* 3*/ ' // comment', /* 4*/ '}', - ], [], false, markers); + ], [r(2, 3, 0)], false, markers); }); - test('Incomplete Regions', () => { + test('Incomplete Regions 2', () => { assertRanges([ /* 1*/ '', /* 2*/ '#region', @@ -291,4 +291,29 @@ suite('Folding with regions', () => { /* 8*/ '', ], [r(1, 7, 0, true), r(3, 5, 0, true)], true, markers); }); + test('Issue 35981', () => { + assertRanges([ + /* 1*/ 'function thisFoldsToEndOfPage() {', + /* 2*/ ' const variable = []', + /* 3*/ ' // #region', + /* 4*/ ' .reduce((a, b) => a,[]);', + /* 5*/ '}', + /* 6*/ '', + /* 7*/ 'function thisFoldsProperly() {', + /* 8*/ ' const foo = "bar"', + /* 9*/ '}', + ], [r(1, 4, 0), r(2, 4, 2), r(7, 8, 0)], false, markers); + }); + test('Misspelled Markers', () => { + assertRanges([ + /* 1*/ '#Region', + /* 2*/ '#endregion', + /* 3*/ '#regionsandmore', + /* 4*/ '#endregion', + /* 5*/ '#region', + /* 6*/ '#end region', + /* 7*/ '#region', + /* 8*/ '#endregionff', + ], [], true, markers); + }); }); \ No newline at end of file diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0ebbab71551..4f7f950ec97 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -53,6 +53,7 @@ export class MenuId { static readonly SCMSourceControl = new MenuId(); static readonly SCMResourceGroupContext = new MenuId(); static readonly SCMResourceContext = new MenuId(); + static readonly SCMChangeContext = new MenuId(); static readonly CommandPalette = new MenuId(); static readonly ViewTitle = new MenuId(); static readonly ViewItemContext = new MenuId(); diff --git a/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts b/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts index 184962e9227..ca45e187101 100644 --- a/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts +++ b/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts @@ -40,6 +40,7 @@ namespace schema { case 'scm/sourceControl': return MenuId.SCMSourceControl; case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; case 'scm/resourceState/context': return MenuId.SCMResourceContext; + case 'scm/change/title': return MenuId.SCMChangeContext; case 'view/title': return MenuId.ViewTitle; case 'view/item/context': return MenuId.ViewItemContext; } diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index d541ed739df..0fb11cd76f5 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -4,94 +4,186 @@ *--------------------------------------------------------------------------------------------*/ import { TPromise } from 'vs/base/common/winjs.base'; -import * as arrays from 'vs/base/common/arrays'; -import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; +import * as types from 'vs/base/common/types'; import URI from 'vs/base/common/uri'; -import { StrictResourceMap } from 'vs/base/common/map'; -import { Workspace } from 'vs/platform/workspace/common/workspace'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Event from 'vs/base/common/event'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { StrictResourceMap } from 'vs/base/common/map'; export const IConfigurationService = createDecorator('configurationService'); +export function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { + return thing + && typeof thing === 'object' + && (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string') + && (!thing.resource || thing.resource instanceof URI); +} + export interface IConfigurationOverrides { overrideIdentifier?: string; resource?: URI; } -export type IConfigurationValues = { [key: string]: IConfigurationValue }; +export enum ConfigurationTarget { + USER = 1, + WORKSPACE, + WORKSPACE_FOLDER, + DEFAULT, + MEMORY +} + +export interface IConfigurationChangeEvent { + + source: ConfigurationTarget; + affectedKeys: string[]; + affectsConfiguration(configuration: string, resource?: URI): boolean; + + // Following data is used for telemetry + sourceConfig: any; + + // Following data is used for Extension host configuration event + changedConfiguration: IConfigurationModel; + changedConfigurationByResource: StrictResourceMap; +} export interface IConfigurationService { _serviceBrand: any; - getConfigurationData(): IConfigurationData; + onDidChangeConfiguration: Event; - /** - * Fetches the appropriate section of the configuration JSON file. - * This will be an object keyed off the section name. - */ - getConfiguration(section?: string, overrides?: IConfigurationOverrides): T; + getConfigurationData(): IConfigurationData; - /** - * Resolves a configuration key to its values in the different scopes - * the setting is defined. - */ - lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue; + getConfiguration(): T; + getConfiguration(section: string): T; + getConfiguration(overrides: IConfigurationOverrides): T; + getConfiguration(section: string, overrides: IConfigurationOverrides): T; - /** - * Returns the defined keys of configurations in the different scopes - * the key is defined. - */ - keys(overrides?: IConfigurationOverrides): IConfigurationKeys; + getValue(key: string, overrides?: IConfigurationOverrides): T; - /** - * Similar to #getConfiguration() but ensures that the latest configuration - * from disk is fetched. - */ - reloadConfiguration(section?: string): TPromise; + updateValue(key: string, value: any): TPromise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise; + updateValue(key: string, value: any, target: ConfigurationTarget): TPromise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): TPromise; - /** - * Event that fires when the configuration changes. - */ - onDidUpdateConfiguration: Event; + reloadConfiguration(): TPromise; + reloadConfiguration(folder: IWorkspaceFolder): TPromise; - /** - * Returns the defined values of configurations in the different scopes. - */ - values(): IConfigurationValues; + inspect(key: string): { + default: T, + user: T, + workspace: T, + workspaceFolder: T, + memory?: T, + value: T, + }; + + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + memory?: string[]; + }; } -export enum ConfigurationSource { - Default = 1, - User, - Workspace +export interface IConfigurationModel { + contents: any; + keys: string[]; + overrides: IOverrides[]; } -export interface IConfigurationServiceEvent { - /** - * The type of source that triggered this event. - */ - source: ConfigurationSource; - /** - * The part of the configuration contributed by the source of this event. - */ - sourceConfig: any; +export interface IOverrides { + contents: any; + identifiers: string[]; } -export interface IConfigurationValue { - value: T; - default: T; - user: T; - workspace: T; - folder: T; +export interface IConfigurationData { + defaults: IConfigurationModel; + user: IConfigurationModel; + workspace: IConfigurationModel; + folders: { [folder: string]: IConfigurationModel }; } -export interface IConfigurationKeys { - default: string[]; - user: string[]; - workspace: string[]; - folder: string[]; +export function compare(from: IConfigurationModel, to: IConfigurationModel): { added: string[], removed: string[], updated: string[] } { + const added = to.keys.filter(key => from.keys.indexOf(key) === -1); + const removed = from.keys.filter(key => to.keys.indexOf(key) === -1); + const updated = []; + + for (const key of from.keys) { + const value1 = getConfigurationValue(from.contents, key); + const value2 = getConfigurationValue(to.contents, key); + if (!objects.equals(value1, value2)) { + updated.push(key); + } + } + + return { added, removed, updated }; +} + +export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { + const root = Object.create(null); + + for (let key in properties) { + addToValueTree(root, key, properties[key], conflictReporter); + } + + return root; +} + +export function addToValueTree(settingsTreeRoot: any, key: string, value: any, conflictReporter: (message: string) => void): void { + const segments = key.split('.'); + const last = segments.pop(); + + let curr = settingsTreeRoot; + for (let i = 0; i < segments.length; i++) { + let s = segments[i]; + let obj = curr[s]; + switch (typeof obj) { + case 'undefined': + obj = curr[s] = Object.create(null); + break; + case 'object': + break; + default: + conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`); + return; + } + curr = obj; + }; + + if (typeof curr === 'object') { + curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 + } else { + conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); + } +} + +export function removeFromValueTree(valueTree: any, key: string): void { + const segments = key.split('.'); + doRemoveFromValueTree(valueTree, segments); +} + +function doRemoveFromValueTree(valueTree: any, segments: string[]): void { + const first = segments.shift(); + if (segments.length === 0) { + // Reached last segment + delete valueTree[first]; + return; + } + + if (Object.keys(valueTree).indexOf(first) !== -1) { + const value = valueTree[first]; + if (typeof value === 'object' && !Array.isArray(value)) { + doRemoveFromValueTree(value, segments); + if (Object.keys(value).length === 0) { + delete valueTree[first]; + } + } + } } /** @@ -129,240 +221,27 @@ export function merge(base: any, add: any, overwrite: boolean): void { }); } -export interface IConfiguraionModel { - contents: T; - keys: string[]; - overrides: IOverrides[]; +export function getConfigurationKeys(): string[] { + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + return Object.keys(properties); } -export interface IOverrides { - contents: T; - identifiers: string[]; +export function getDefaultValues(): any { + const valueTreeRoot: any = Object.create(null); + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + + for (let key in properties) { + let value = properties[key].default; + addToValueTree(valueTreeRoot, key, value, message => console.error(`Conflict in default settings: ${message}`)); + } + + return valueTreeRoot; } -export class ConfigurationModel implements IConfiguraionModel { - - constructor(protected _contents: T = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { - } - - public get contents(): T { - return this._contents; - } - - public get overrides(): IOverrides[] { - return this._overrides; - } - - public get keys(): string[] { - return this._keys; - } - - public getContentsFor(section: string): V { - return objects.clone(this.contents[section]); - } - - public override(identifier: string): ConfigurationModel { - const result = new ConfigurationModel(); - const contents = objects.clone(this.contents); - if (this._overrides) { - for (const override of this._overrides) { - if (override.identifiers.indexOf(identifier) !== -1) { - merge(contents, override.contents, true); - } - } - } - result._contents = contents; - return result; - } - - public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { - const mergedModel = new ConfigurationModel(); - this.doMerge(mergedModel, this, overwrite); - this.doMerge(mergedModel, other, overwrite); - return mergedModel; - } - - protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) { - merge(source.contents, objects.clone(target.contents), overwrite); - const overrides = objects.clone(source._overrides); - for (const override of target._overrides) { - const [sourceOverride] = overrides.filter(o => arrays.equals(o.identifiers, override.identifiers)); - if (sourceOverride) { - merge(sourceOverride.contents, override.contents, overwrite); - } else { - overrides.push(override); - } - } - source._overrides = overrides; - } +export function overrideIdentifierFromKey(key: string): string { + return key.substring(1, key.length - 1); } -export interface IConfigurationData { - defaults: IConfiguraionModel; - user: IConfiguraionModel; - workspace: IConfiguraionModel; - folders: { [folder: string]: IConfiguraionModel }; +export function keyFromOverrideIdentifier(overrideIdentifier: string): string { + return `[${overrideIdentifier}]`; } - -export class Configuration { - - private _globalConfiguration: ConfigurationModel; - private _workspaceConsolidatedConfiguration: ConfigurationModel; - protected _foldersConsolidatedConfigurations: StrictResourceMap>; - - constructor(protected _defaults: ConfigurationModel, protected _user: ConfigurationModel, protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), protected folders: StrictResourceMap> = new StrictResourceMap>(), protected _workspace?: Workspace) { - this.merge(); - } - - get defaults(): ConfigurationModel { - return this._defaults; - } - - get user(): ConfigurationModel { - return this._user; - } - - protected merge(): void { - this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); - this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); - this._foldersConsolidatedConfigurations = new StrictResourceMap>(); - for (const folder of this.folders.keys()) { - this.mergeFolder(folder); - } - } - - protected mergeFolder(folder: URI) { - this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); - } - - getValue(section: string = '', overrides: IConfigurationOverrides = {}): C { - const configModel = this.getConsolidateConfigurationModel(overrides); - return section ? configModel.getContentsFor(section) : configModel.contents; - } - - lookup(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue { - // make sure to clone the configuration so that the receiver does not tamper with the values - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); - const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); - return { - default: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key)), - user: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key)), - workspace: objects.clone(this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0), //Check on workspace exists or not because _workspaceConfiguration is never null - folder: objects.clone(folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0), - value: objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)) - }; - } - - keys(overrides: IConfigurationOverrides = {}): IConfigurationKeys { - const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); - return { - default: this._defaults.keys, - user: this._user.keys, - workspace: this._workspaceConfiguration.keys, - folder: folderConfigurationModel ? folderConfigurationModel.keys : [] - }; - } - - values(): IConfigurationValues { - const result = Object.create(null); - const keyset = this.keys(); - const keys = [...keyset.workspace, ...keyset.user, ...keyset.default].sort(); - - let lastKey: string; - for (const key of keys) { - if (key !== lastKey) { - lastKey = key; - result[key] = this.lookup(key); - } - } - - return result; - } - - values2(): Map> { - const result: Map> = new Map>(); - const keyset = this.keys(); - const keys = [...keyset.workspace, ...keyset.user, ...keyset.default].sort(); - - let lastKey: string; - for (const key of keys) { - if (key !== lastKey) { - lastKey = key; - result.set(key, this.lookup(key)); - } - } - - return result; - } - - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { - let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides); - return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; - } - - private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides): ConfigurationModel { - if (!this._workspace) { - return this._globalConfiguration; - } - - if (!resource) { - return this._workspaceConsolidatedConfiguration; - } - - const root = this._workspace.getFolder(resource); - if (!root) { - return this._workspaceConsolidatedConfiguration; - } - - return this._foldersConsolidatedConfigurations.get(root.uri) || this._workspaceConsolidatedConfiguration; - } - - private getFolderConfigurationModelForResource(resource: URI): ConfigurationModel { - if (!this._workspace || !resource) { - return null; - } - - const root = this._workspace.getFolder(resource); - return root ? this.folders.get(root.uri) : null; - } - - public toData(): IConfigurationData { - return { - defaults: { - contents: this._defaults.contents, - overrides: this._defaults.overrides, - keys: this._defaults.keys - }, - user: { - contents: this._user.contents, - overrides: this._user.overrides, - keys: this._user.keys - }, - workspace: { - contents: this._workspaceConfiguration.contents, - overrides: this._workspaceConfiguration.overrides, - keys: this._workspaceConfiguration.keys - }, - folders: this.folders.keys().reduce((result, folder) => { - const { contents, overrides, keys } = this.folders.get(folder); - result[folder.toString()] = { contents, overrides, keys }; - return result; - }, Object.create({})) - }; - } - - public static parse(data: IConfigurationData, workspace: Workspace): Configuration { - const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults); - const userConfiguration = Configuration.parseConfigurationModel(data.user); - const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace); - const folders: StrictResourceMap> = Object.keys(data.folders).reduce((result, key) => { - result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key])); - return result; - }, new StrictResourceMap>()); - return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, workspace); - } - - private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { - return new ConfigurationModel(model.contents, model.keys, model.overrides); - } -} \ No newline at end of file diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts new file mode 100644 index 00000000000..56ec4b29832 --- /dev/null +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -0,0 +1,601 @@ +/*--------------------------------------------------------------------------------------------- + * 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 json from 'vs/base/common/json'; +import { StrictResourceMap } from 'vs/base/common/map'; +import * as arrays from 'vs/base/common/arrays'; +import * as objects from 'vs/base/common/objects'; +import URI from 'vs/base/common/uri'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, merge, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree } from 'vs/platform/configuration/common/configuration'; +import { Workspace } from 'vs/platform/workspace/common/workspace'; + +export class ConfigurationModel implements IConfigurationModel { + + constructor(protected _contents: any = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { + } + + public get contents(): any { + return this._contents; + } + + public get overrides(): IOverrides[] { + return this._overrides; + } + + public get keys(): string[] { + return this._keys; + } + + public getSectionContents(section: string): V { + return this.contents[section]; + } + + public setValue(key: string, value: any) { + this.addKey(key); + addToValueTree(this._contents, key, value, e => { throw new Error(e); }); + } + + public removeValue(key: string): void { + if (this.removeKey(key)) { + removeFromValueTree(this._contents, key); + } + } + + public setValueInOverrides(overrideIdentifier: string, key: string, value: any): void { + let override = this._overrides.filter(override => override.identifiers.indexOf(overrideIdentifier) !== -1)[0]; + if (!override) { + override = { identifiers: [overrideIdentifier], contents: {} }; + this._overrides.push(override); + } + addToValueTree(override.contents, key, value, e => { throw new Error(e); }); + } + + public override(identifier: string): ConfigurationModel { + const overrideContents = this.getContentsForOverrideIdentifer(identifier); + + if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) { + // If there are no valid overrides, use base contents + return new ConfigurationModel(this._contents); + } + + let contents = {}; + for (const key of arrays.distinct([...Object.keys(this._contents), ...Object.keys(overrideContents)])) { + + let contentsForKey = this._contents[key]; + let overrideContentsForKey = overrideContents[key]; + + // If there are override contents for the key, clone and merge otherwise use base contents + if (overrideContentsForKey) { + // Clone and merge only if base contents and override contents are of type object otherwise just override + if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') { + contentsForKey = objects.clone(contentsForKey); + merge(contentsForKey, overrideContentsForKey, true); + } else { + contentsForKey = overrideContentsForKey; + } + } + + contents[key] = contentsForKey; + } + return new ConfigurationModel(contents); + } + + public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { + const mergedModel = new ConfigurationModel(); + this.doMerge(mergedModel, this, overwrite); + this.doMerge(mergedModel, other, overwrite); + return mergedModel; + } + + protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) { + merge(source.contents, objects.clone(target.contents), overwrite); + const overrides = objects.clone(source._overrides); + for (const override of target._overrides) { + const [sourceOverride] = overrides.filter(o => arrays.equals(o.identifiers, override.identifiers)); + if (sourceOverride) { + merge(sourceOverride.contents, override.contents, overwrite); + } else { + overrides.push(override); + } + } + source._overrides = overrides; + } + + private getContentsForOverrideIdentifer(identifier: string): any { + for (const override of this._overrides) { + if (override.identifiers.indexOf(identifier) !== -1) { + return override.contents; + } + } + return null; + } + + private addKey(key: string): void { + let index = this._keys.length; + for (let i = 0; i < index; i++) { + if (key.indexOf(this._keys[i]) === 0) { + index = i; + } + } + this._keys.splice(index, 1, key); + } + + private removeKey(key: string): boolean { + let index = this._keys.indexOf(key); + if (index !== -1) { + this._keys.splice(index, 1); + return true; + } + return false; + } + + toJSON(): IConfigurationModel { + return { + contents: this.contents, + overrides: this.overrides, + keys: this.keys + }; + } +} + +export class DefaultConfigurationModel extends ConfigurationModel { + + constructor() { + super(getDefaultValues()); + this._keys = getConfigurationKeys(); + this._overrides = Object.keys(this._contents) + .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) + .map(key => { + return { + identifiers: [overrideIdentifierFromKey(key).trim()], + contents: toValuesTree(this._contents[key], message => console.error(`Conflict in default settings file: ${message}`)) + }; + }); + } + + public get keys(): string[] { + return this._keys; + } +} + +interface Overrides extends IOverrides { + raw: any; +} + +export class CustomConfigurationModel extends ConfigurationModel { + + protected _parseErrors: any[] = []; + + constructor(content: string = '', private name: string = '') { + super(); + if (content) { + this.update(content); + } + } + + public get errors(): any[] { + return this._parseErrors; + } + + public update(content: string): void { + let parsed: any = {}; + let overrides: Overrides[] = []; + let currentProperty: string = null; + let currentParent: any = []; + let previousParents: any[] = []; + let parseErrors: json.ParseError[] = []; + + function onValue(value: any) { + if (Array.isArray(currentParent)) { + (currentParent).push(value); + } else if (currentProperty) { + currentParent[currentProperty] = value; + } + if (OVERRIDE_PROPERTY_PATTERN.test(currentProperty)) { + onOverrideSettingsValue(currentProperty, value); + } + } + + function onOverrideSettingsValue(property: string, value: any): void { + overrides.push({ + identifiers: [overrideIdentifierFromKey(property).trim()], + raw: value, + contents: null + }); + } + + let visitor: json.JSONVisitor = { + onObjectBegin: () => { + let object = {}; + onValue(object); + previousParents.push(currentParent); + currentParent = object; + currentProperty = null; + }, + onObjectProperty: (name: string) => { + currentProperty = name; + }, + onObjectEnd: () => { + currentParent = previousParents.pop(); + }, + onArrayBegin: () => { + let array: any[] = []; + onValue(array); + previousParents.push(currentParent); + currentParent = array; + currentProperty = null; + }, + onArrayEnd: () => { + currentParent = previousParents.pop(); + }, + onLiteralValue: onValue, + onError: (error: json.ParseErrorCode) => { + parseErrors.push({ error: error }); + } + }; + if (content) { + try { + json.visit(content, visitor); + parsed = currentParent[0] || {}; + } catch (e) { + console.error(`Error while parsing settings file ${this.name}: ${e}`); + this._parseErrors = [e]; + } + } + this.processRaw(parsed); + + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + this._overrides = overrides.map(override => { + // Filter unknown and non-overridable properties + const raw = {}; + for (const key in override.raw) { + if (configurationProperties[key] && configurationProperties[key].overridable) { + raw[key] = override.raw[key]; + } + } + return { + identifiers: override.identifiers, + contents: toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)) + }; + }); + } + + protected processRaw(raw: any): void { + this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)); + this._keys = Object.keys(raw); + } +} + +export class Configuration { + + private _globalConfiguration: ConfigurationModel; + private _workspaceConsolidatedConfiguration: ConfigurationModel; + protected _foldersConsolidatedConfigurations: StrictResourceMap; + + constructor(protected _defaults: ConfigurationModel, + protected _user: ConfigurationModel, + protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), + protected folders: StrictResourceMap = new StrictResourceMap(), + protected _memoryConfiguration: ConfigurationModel = new ConfigurationModel(), + protected _memoryConfigurationByResource: StrictResourceMap = new StrictResourceMap(), + protected _workspace?: Workspace) { + this.merge(); + } + + get defaults(): ConfigurationModel { + return this._defaults; + } + + get user(): ConfigurationModel { + return this._user; + } + + get workspace(): ConfigurationModel { + return this._workspaceConfiguration; + } + + protected merge(): void { + this._globalConfiguration = this._defaults.merge(this._user); + this.updateWorkspaceConsolidateConfiguration(); + this._foldersConsolidatedConfigurations = new StrictResourceMap(); + for (const folder of this.folders.keys()) { + this.mergeFolder(folder); + } + } + + private updateWorkspaceConsolidateConfiguration() { + this._workspaceConsolidatedConfiguration = this._globalConfiguration.merge(this._workspaceConfiguration).merge(this._memoryConfiguration); + } + + protected mergeFolder(folder: URI) { + this._foldersConsolidatedConfigurations.set(folder, this._workspaceConsolidatedConfiguration.merge(this.folders.get(folder))); + } + + getSection(section: string = '', overrides: IConfigurationOverrides = {}): C { + const configModel = this.getConsolidateConfigurationModel(overrides); + return objects.clone(section ? configModel.getSectionContents(section) : configModel.contents); + } + + getValue(key: string, overrides: IConfigurationOverrides = {}): any { + const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); + return objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)); + } + + updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { + let memoryConfiguration: ConfigurationModel; + if (overrides.resource) { + memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); + if (!memoryConfiguration) { + memoryConfiguration = new ConfigurationModel(); + this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration); + } + } else { + memoryConfiguration = this._memoryConfiguration; + } + + if (value === void 0) { + memoryConfiguration.removeValue(key); + } else { + memoryConfiguration.setValue(key, value); + } + + if (!overrides.resource) { + this.updateWorkspaceConsolidateConfiguration(); + } + } + + lookup(key: string, overrides: IConfigurationOverrides = {}): { + default: C, + user: C, + workspace: C, + workspaceFolder: C + memory?: C + value: C, + } { + const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); + const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); + const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; + return objects.clone({ + default: getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key), + user: getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key), + workspace: this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0, //Check on workspace exists or not because _workspaceConfiguration is never null + workspaceFolder: folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0, + memory: getConfigurationValue(overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).contents : memoryConfigurationModel.contents, key), + value: getConfigurationValue(consolidateConfigurationModel.contents, key) + }); + } + + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + } { + const folderConfigurationModel = this.getFolderConfigurationModelForResource(); + return objects.clone({ + default: this._defaults.keys, + user: this._user.keys, + workspace: this._workspaceConfiguration.keys, + workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys : [] + }); + } + + private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { + let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides); + return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; + } + + private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides): ConfigurationModel { + if (!this._workspace) { + return this._globalConfiguration; + } + + if (!resource) { + return this._workspaceConsolidatedConfiguration; + } + + let consolidateConfiguration = this._workspaceConsolidatedConfiguration; + const root = this._workspace.getFolder(resource); + if (root) { + consolidateConfiguration = this._foldersConsolidatedConfigurations.get(root.uri) || this._workspaceConsolidatedConfiguration; + } + + const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource); + if (memoryConfigurationForResource) { + consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource); + } + + return consolidateConfiguration; + } + + private getFolderConfigurationModelForResource(resource?: URI): ConfigurationModel { + if (!this._workspace || !resource) { + return null; + } + + const root = this._workspace.getFolder(resource); + return root ? this.folders.get(root.uri) : null; + } + + public toData(): IConfigurationData { + return { + defaults: { + contents: this._defaults.contents, + overrides: this._defaults.overrides, + keys: this._defaults.keys + }, + user: { + contents: this._user.contents, + overrides: this._user.overrides, + keys: this._user.keys + }, + workspace: { + contents: this._workspaceConfiguration.contents, + overrides: this._workspaceConfiguration.overrides, + keys: this._workspaceConfiguration.keys + }, + folders: this.folders.keys().reduce((result, folder) => { + const { contents, overrides, keys } = this.folders.get(folder); + result[folder.toString()] = { contents, overrides, keys }; + return result; + }, Object.create({})) + }; + } + + public static parse(data: IConfigurationData, workspace: Workspace): Configuration { + const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults); + const userConfiguration = Configuration.parseConfigurationModel(data.user); + const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace); + const folders: StrictResourceMap = Object.keys(data.folders).reduce((result, key) => { + result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key])); + return result; + }, new StrictResourceMap()); + return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, new ConfigurationModel(), new StrictResourceMap(), workspace); + } + + private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { + return new ConfigurationModel(model.contents, model.keys, model.overrides); + } +} + +export class AbstractConfigurationChangeEvent { + + protected doesConfigurationContains(configuration: ConfigurationModel, config: string): boolean { + let changedKeysTree = configuration.contents; + let requestedTree = toValuesTree({ [config]: true }, () => { }); + + let key; + while (typeof requestedTree === 'object' && (key = Object.keys(requestedTree)[0])) { // Only one key should present, since we added only one property + changedKeysTree = changedKeysTree[key]; + if (!changedKeysTree) { + return false; // Requested tree is not found + } + requestedTree = requestedTree[key]; + } + return true; + } + + protected updateKeys(configuration: ConfigurationModel, keys: string[], resource?: URI): void { + for (const key of keys) { + configuration.setValue(key, true); + } + } +} + +export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { + + private _changedConfiguration: ConfigurationModel = null; + + constructor(readonly affectedKeys: string[], readonly source: ConfigurationTarget, readonly sourceConfig: any) { super(); } + + get changedConfiguration(): ConfigurationModel { + if (!this._changedConfiguration) { + this._changedConfiguration = new ConfigurationModel(); + this.updateKeys(this._changedConfiguration, this.affectedKeys); + } + return this._changedConfiguration; + } + + get changedConfigurationByResource(): StrictResourceMap { + return new StrictResourceMap(); + } + + affectsConfiguration(config: string, resource?: URI): boolean { + return this.doesConfigurationContains(this.changedConfiguration, config); + } +} + +export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { + + private _source: ConfigurationTarget; + private _sourceConfig: any; + + constructor( + private _changedConfiguration: ConfigurationModel = new ConfigurationModel(), + private _changedConfigurationByResource: StrictResourceMap = new StrictResourceMap()) { + super(); + } + + get changedConfiguration(): IConfigurationModel { + return this._changedConfiguration; + } + + get changedConfigurationByResource(): StrictResourceMap { + return this._changedConfigurationByResource; + } + + change(event: ConfigurationChangeEvent): ConfigurationChangeEvent + change(keys: string[], resource?: URI): ConfigurationChangeEvent + change(arg1: any, arg2?: any): ConfigurationChangeEvent { + if (arg1 instanceof ConfigurationChangeEvent) { + this._changedConfiguration = this._changedConfiguration.merge(arg1._changedConfiguration); + for (const resource of this.changedConfigurationByResource.keys()) { + let changedConfigurationByResource = this.getOrSetChangedConfigurationForResource(resource); + changedConfigurationByResource = changedConfigurationByResource.merge(arg1._changedConfigurationByResource.get(resource)); + this._changedConfigurationByResource.set(resource, changedConfigurationByResource); + } + } + this.changeWithKeys(arg1, arg2); + return this; + } + + telemetryData(source: ConfigurationTarget, sourceConfig: any): ConfigurationChangeEvent { + this._source = source; + this._sourceConfig = sourceConfig; + return this; + } + + get affectedKeys(): string[] { + const keys = [...this._changedConfiguration.keys]; + this._changedConfigurationByResource.forEach(model => keys.push(...model.keys)); + return keys; + } + + get source(): ConfigurationTarget { + return this._source; + } + + get sourceConfig(): any { + return this._sourceConfig; + } + + affectsConfiguration(config: string, resource?: URI): boolean { + let configurationModelsToSearch: ConfigurationModel[] = [this._changedConfiguration]; + + if (resource) { + let model = this._changedConfigurationByResource.get(resource); + if (model) { + configurationModelsToSearch.push(model); + } + } else { + configurationModelsToSearch.push(...this._changedConfigurationByResource.values()); + } + + for (const configuration of configurationModelsToSearch) { + if (this.doesConfigurationContains(configuration, config)) { + return true; + } + } + + return false; + } + + private changeWithKeys(keys: string[], resource?: URI): void { + let changedConfiguration = resource ? this.getOrSetChangedConfigurationForResource(resource) : this._changedConfiguration; + this.updateKeys(changedConfiguration, keys); + } + + private getOrSetChangedConfigurationForResource(resource: URI): ConfigurationModel { + let changedConfigurationByResource = this._changedConfigurationByResource.get(resource); + if (!changedConfigurationByResource) { + changedConfigurationByResource = new ConfigurationModel(); + this._changedConfigurationByResource.set(resource, changedConfigurationByResource); + } + return changedConfigurationByResource; + } +} \ No newline at end of file diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 52d651b43d7..c818c1f454f 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -38,10 +38,10 @@ export interface IConfigurationRegistry { registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void; /** - * Event that fires whenver a configuratio has been + * Event that fires whenver a configuration has been * registered. */ - onDidRegisterConfiguration: Event; + onDidRegisterConfiguration: Event; /** * Returns all configuration nodes contributed to this registry. @@ -96,40 +96,39 @@ export const editorConfigurationSchemaId = 'vscode://schemas/settings/editor'; const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); class ConfigurationRegistry implements IConfigurationRegistry { + private configurationContributors: IConfigurationNode[]; private configurationProperties: { [qualifiedKey: string]: IJSONSchema }; private editorConfigurationSchema: IJSONSchema; - private _onDidRegisterConfiguration: Emitter; private overrideIdentifiers: string[] = []; private overridePropertyPattern: string; + private _onDidRegisterConfiguration: Emitter = new Emitter(); + readonly onDidRegisterConfiguration: Event = this._onDidRegisterConfiguration.event; + constructor() { this.configurationContributors = []; this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting' }; - this._onDidRegisterConfiguration = new Emitter(); this.configurationProperties = {}; this.computeOverridePropertyPattern(); contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema); } - public get onDidRegisterConfiguration() { - return this._onDidRegisterConfiguration.event; - } - public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void { this.registerConfigurations([configuration], validate); } public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void { + const properties: string[] = []; configurations.forEach(configuration => { - this.validateAndRegisterProperties(configuration, validate); // fills in defaults + properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults this.configurationContributors.push(configuration); this.registerJSONConfiguration(configuration); this.updateSchemaForOverrideSettingsConfiguration(configuration); }); - this._onDidRegisterConfiguration.fire(this); + this._onDidRegisterConfiguration.fire(properties); } public notifyConfigurationSchemaUpdated(configuration: IConfigurationNode) { @@ -165,9 +164,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } - private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false) { + private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] { scope = configuration.scope !== void 0 && configuration.scope !== null ? configuration.scope : scope; overridable = configuration.overridable || overridable; + let propertyKeys = []; let properties = configuration.properties; if (properties) { for (let key in properties) { @@ -192,14 +192,16 @@ class ConfigurationRegistry implements IConfigurationRegistry { } // add to properties map this.configurationProperties[key] = properties[key]; + propertyKeys.push(key); } } let subNodes = configuration.allOf; if (subNodes) { for (let node of subNodes) { - this.validateAndRegisterProperties(node, validate, scope, overridable); + propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope, overridable)); } } + return propertyKeys; } validateProperty(property: string): boolean { @@ -314,4 +316,4 @@ export function validateProperty(property: string): string { return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property); } return null; -} +} \ No newline at end of file diff --git a/src/vs/platform/configuration/common/model.ts b/src/vs/platform/configuration/common/model.ts deleted file mode 100644 index 53b122efd0e..00000000000 --- a/src/vs/platform/configuration/common/model.ts +++ /dev/null @@ -1,202 +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/registry/common/platform'; -import * as json from 'vs/base/common/json'; -import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; -import { ConfigurationModel, IOverrides } from 'vs/platform/configuration/common/configuration'; - -export function getDefaultValues(): any { - const valueTreeRoot: any = Object.create(null); - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - - for (let key in properties) { - let value = properties[key].default; - addToValueTree(valueTreeRoot, key, value, message => console.error(`Conflict in default settings: ${message}`)); - } - - return valueTreeRoot; -} - -export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { - const root = Object.create(null); - - for (let key in properties) { - addToValueTree(root, key, properties[key], conflictReporter); - } - - return root; -} - -function addToValueTree(settingsTreeRoot: any, key: string, value: any, conflictReporter: (message: string) => void): void { - const segments = key.split('.'); - const last = segments.pop(); - - let curr = settingsTreeRoot; - for (let i = 0; i < segments.length; i++) { - let s = segments[i]; - let obj = curr[s]; - switch (typeof obj) { - case 'undefined': - obj = curr[s] = Object.create(null); - break; - case 'object': - break; - default: - conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`); - return; - } - curr = obj; - }; - - if (typeof curr === 'object') { - curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 - } else { - conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); - } -} - -export function getConfigurationKeys(): string[] { - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - - return Object.keys(properties); -} - -export class DefaultConfigurationModel extends ConfigurationModel { - - constructor() { - super(getDefaultValues()); - this._keys = getConfigurationKeys(); - this._overrides = Object.keys(this._contents) - .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) - .map(key => { - return >{ - identifiers: [overrideIdentifierFromKey(key).trim()], - contents: toValuesTree(this._contents[key], message => console.error(`Conflict in default settings file: ${message}`)) - }; - }); - } - - public get keys(): string[] { - return this._keys; - } -} - -interface Overrides extends IOverrides { - raw: any; -} - -export class CustomConfigurationModel extends ConfigurationModel { - - protected _parseErrors: any[] = []; - - constructor(content: string = '', private name: string = '') { - super(); - if (content) { - this.update(content); - } - } - - public get errors(): any[] { - return this._parseErrors; - } - - public update(content: string): void { - let parsed: T = {}; - let overrides: Overrides[] = []; - let currentProperty: string = null; - let currentParent: any = []; - let previousParents: any[] = []; - let parseErrors: json.ParseError[] = []; - - function onValue(value: any) { - if (Array.isArray(currentParent)) { - (currentParent).push(value); - } else if (currentProperty) { - currentParent[currentProperty] = value; - } - if (OVERRIDE_PROPERTY_PATTERN.test(currentProperty)) { - onOverrideSettingsValue(currentProperty, value); - } - } - - function onOverrideSettingsValue(property: string, value: any): void { - overrides.push({ - identifiers: [overrideIdentifierFromKey(property).trim()], - raw: value, - contents: null - }); - } - - let visitor: json.JSONVisitor = { - onObjectBegin: () => { - let object = {}; - onValue(object); - previousParents.push(currentParent); - currentParent = object; - currentProperty = null; - }, - onObjectProperty: (name: string) => { - currentProperty = name; - }, - onObjectEnd: () => { - currentParent = previousParents.pop(); - }, - onArrayBegin: () => { - let array: any[] = []; - onValue(array); - previousParents.push(currentParent); - currentParent = array; - currentProperty = null; - }, - onArrayEnd: () => { - currentParent = previousParents.pop(); - }, - onLiteralValue: onValue, - onError: (error: json.ParseErrorCode) => { - parseErrors.push({ error: error }); - } - }; - if (content) { - try { - json.visit(content, visitor); - parsed = currentParent[0] || {}; - } catch (e) { - console.error(`Error while parsing settings file ${this.name}: ${e}`); - this._parseErrors = [e]; - } - } - this.processRaw(parsed); - - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - this._overrides = overrides.map>(override => { - // Filter unknown and non-overridable properties - const raw = {}; - for (const key in override.raw) { - if (configurationProperties[key] && configurationProperties[key].overridable) { - raw[key] = override.raw[key]; - } - } - return { - identifiers: override.identifiers, - contents: toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)) - }; - }); - } - - protected processRaw(raw: T): void { - this._contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`)); - this._keys = Object.keys(raw); - } -} - -export function overrideIdentifierFromKey(key: string): string { - return key.substring(1, key.length - 1); -} - -export function keyFromOverrideIdentifier(overrideIdentifier: string): string { - return `[${overrideIdentifier}]`; -} \ No newline at end of file diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 8f6005c6430..9fb8cfc6fa6 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -4,26 +4,28 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ConfigWatcher } from 'vs/base/node/config'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { ConfigurationSource, IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, IConfigurationKeys, ConfigurationModel, IConfigurationOverrides, Configuration, IConfigurationValues, IConfigurationData } from 'vs/platform/configuration/common/configuration'; -import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { CustomConfigurationModel, DefaultConfigurationModel, ConfigurationModel, Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import Event, { Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { equals } from 'vs/base/common/objects'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { +export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { _serviceBrand: any; - private _configuration: Configuration; - private userConfigModelWatcher: ConfigWatcher>; + private _configuration: Configuration; + private userConfigModelWatcher: ConfigWatcher; - private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; constructor( @IEnvironmentService environmentService: IEnvironmentService @@ -31,70 +33,111 @@ export class ConfigurationService extends Disposable implements IConfiguratio super(); this.userConfigModelWatcher = new ConfigWatcher(environmentService.appSettingsPath, { - changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new CustomConfigurationModel(null, environmentService.appSettingsPath), parse: (content: string, parseErrors: any[]) => { - const userConfigModel = new CustomConfigurationModel(content, environmentService.appSettingsPath); + changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new CustomConfigurationModel(null, environmentService.appSettingsPath), parse: (content: string, parseErrors: any[]) => { + const userConfigModel = new CustomConfigurationModel(content, environmentService.appSettingsPath); parseErrors = [...userConfigModel.errors]; return userConfigModel; } }); this._register(this.userConfigModelWatcher); + this.reset(); + // Listeners - this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this.onConfigurationChange(ConfigurationSource.User))); - this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(() => this.onConfigurationChange(ConfigurationSource.Default))); + this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this.onDidUpdateConfigModel())); + this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDidRegisterConfiguration(configurationProperties))); } - public configuration(): Configuration { - return this._configuration || (this._configuration = this.consolidateConfigurations()); + get configuration(): Configuration { + return this._configuration; } - private onConfigurationChange(source: ConfigurationSource): void { + getConfigurationData(): IConfigurationData { + return this.configuration.toData(); + } + + getConfiguration(): T + getConfiguration(section: string): T + getConfiguration(overrides: IConfigurationOverrides): T + getConfiguration(section: string, overrides: IConfigurationOverrides): T + getConfiguration(arg1?: any, arg2?: any): any { + const section = typeof arg1 === 'string' ? arg1 : void 0; + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; + return this.configuration.getSection(section, overrides); + } + + getValue(key: string, overrides: IConfigurationOverrides): any { + return this.configuration.getValue(key, overrides); + } + + updateValue(key: string, value: any): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise + updateValue(key: string, value: any, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, arg3?: any, arg4?: any): TPromise { + return TPromise.wrapError(new Error('not supported')); + } + + inspect(key: string): { + default: T, + user: T, + workspace: T, + workspaceFolder: T + value: T + } { + return this.configuration.lookup(key); + } + + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + } { + return this.configuration.keys(); + } + + reloadConfiguration(folder?: IWorkspaceFolder): TPromise { + return folder ? TPromise.as(null) : + new TPromise((c, e) => this.userConfigModelWatcher.reload(() => c(this.onDidUpdateConfigModel()))); + } + + private onDidUpdateConfigModel(): void { + let changedKeys = []; + const { added, updated, removed } = compare(this._configuration.user, this.userConfigModelWatcher.getConfig()); + changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = this._configuration; + this.reset(); + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this._configuration.getValue(key))); + if (changedKeys.length) { + this.trigger(changedKeys, ConfigurationTarget.USER); + } + } + } + + private onDidRegisterConfiguration(keys: string[]): void { this.reset(); // reset our caches - - const cache = this.configuration(); - - this._onDidUpdateConfiguration.fire({ - source, - sourceConfig: source === ConfigurationSource.Default ? cache.defaults.contents : cache.user.contents - }); - } - - public reloadConfiguration(section?: string): TPromise { - return new TPromise(c => { - this.userConfigModelWatcher.reload(() => { - this.reset(); // reset our caches - c(this.getConfiguration(section)); - }); - }); - } - - public getConfiguration(section?: string, options?: IConfigurationOverrides): C { - return this.configuration().getValue(section, options); - } - - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { - return this.configuration().lookup(key, overrides); - } - - public keys(overrides?: IConfigurationOverrides): IConfigurationKeys { - return this.configuration().keys(overrides); - } - - public values(): IConfigurationValues { - return this._configuration.values(); - } - - public getConfigurationData(): IConfigurationData { - return this.configuration().toData(); + this.trigger(keys, ConfigurationTarget.DEFAULT); } private reset(): void { - this._configuration = this.consolidateConfigurations(); + const defaults = new DefaultConfigurationModel(); + const user = this.userConfigModelWatcher.getConfig(); + this._configuration = new Configuration(defaults, user); } - private consolidateConfigurations(): Configuration { - const defaults = new DefaultConfigurationModel(); - const user = this.userConfigModelWatcher.getConfig(); - return new Configuration(defaults, user); + private trigger(keys: string[], source: ConfigurationTarget): void { + this._onDidChangeConfiguration.fire(new ConfigurationChangeEvent().change(keys).telemetryData(source, this.getTargetConfiguration(source))); + } + + private getTargetConfiguration(target: ConfigurationTarget): any { + switch (target) { + case ConfigurationTarget.DEFAULT: + return this._configuration.defaults.contents; + case ConfigurationTarget.USER: + return this._configuration.user.contents; + } + return {}; } } \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/configuration.model.test.ts b/src/vs/platform/configuration/test/common/configuration.model.test.ts deleted file mode 100644 index 42b52919b33..00000000000 --- a/src/vs/platform/configuration/test/common/configuration.model.test.ts +++ /dev/null @@ -1,87 +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 * as assert from 'assert'; -import { ConfigurationModel } from 'vs/platform/configuration/common/configuration'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; - -suite('Configuration', () => { - - suiteSetup(() => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'a', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - 'a': { - 'description': 'a', - 'type': 'boolean', - 'default': true, - 'overridable': true - } - } - }); - }); - - test('simple merge', () => { - let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); - let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); - }); - - test('recursive merge', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - }); - - test('simple merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': 2 } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'b': 2 } }]); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 } }]); - }); - - test('recursive merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]); - }); - - test('merge ignore keys', () => { - let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); - let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); - let result = base.merge(add); - assert.deepEqual(result.keys, []); - }); - - test('Test contents while getting an existing property', () => { - let testObject = new ConfigurationModel({ 'a': 1 }); - assert.deepEqual(testObject.getContentsFor('a'), 1); - - testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); - assert.deepEqual(testObject.getContentsFor('a'), { 'b': 1 }); - }); - - test('Test contents are undefined for non existing properties', () => { - const testObject = new ConfigurationModel({ awesome: true }); - - assert.deepEqual(testObject.getContentsFor('unknownproperty'), undefined); - }); - - test('Test override gives all content merged with overrides', () => { - const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 } }]); - - assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); - }); -}); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/configuration.test.ts b/src/vs/platform/configuration/test/common/configuration.test.ts index 11024298132..456df853e01 100644 --- a/src/vs/platform/configuration/test/common/configuration.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { merge } from 'vs/platform/configuration/common/configuration'; +import { merge, removeFromValueTree } from 'vs/platform/configuration/common/configuration'; suite('Configuration', () => { @@ -18,5 +18,92 @@ suite('Configuration', () => { assert.deepEqual(base, { 'a': 1, 'b': 2, 'c': 4 }); }); + test('removeFromValueTree: remove a non existing key', () => { + let target = { 'a': { 'b': 2 } }; + + removeFromValueTree(target, 'c'); + + assert.deepEqual(target, { 'a': { 'b': 2 } }); + }); + + test('removeFromValueTree: remove a multi segmented key from an object that has only sub sections of the key', () => { + let target = { 'a': { 'b': 2 } }; + + removeFromValueTree(target, 'a.b.c'); + + assert.deepEqual(target, { 'a': { 'b': 2 } }); + }); + + test('removeFromValueTree: remove a single segemented key', () => { + let target = { 'a': 1 }; + + removeFromValueTree(target, 'a'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a single segemented key when its value is undefined', () => { + let target = { 'a': void 0 }; + + removeFromValueTree(target, 'a'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key when its value is undefined', () => { + let target = { 'a': { 'b': 1 } }; + + removeFromValueTree(target, 'a.b'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key when its value is array', () => { + let target = { 'a': { 'b': [1] } }; + + removeFromValueTree(target, 'a.b'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key first segment value is array', () => { + let target = { 'a': [1] }; + + removeFromValueTree(target, 'a.0'); + + assert.deepEqual(target, { 'a': [1] }); + }); + + test('removeFromValueTree: remove when key is the first segmenet', () => { + let target = { 'a': { 'b': 1 } }; + + removeFromValueTree(target, 'a'); + + assert.deepEqual(target, {}); + }); + + test('removeFromValueTree: remove a multi segemented key when the first node has more values', () => { + let target = { 'a': { 'b': { 'c': 1 }, 'd': 1 } }; + + removeFromValueTree(target, 'a.b.c'); + + assert.deepEqual(target, { 'a': { 'd': 1 } }); + }); + + test('removeFromValueTree: remove a multi segemented key when in between node has more values', () => { + let target = { 'a': { 'b': { 'c': { 'd': 1 }, 'd': 1 } } }; + + removeFromValueTree(target, 'a.b.c.d'); + + assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); + }); + + test('removeFromValueTree: remove a multi segemented key when the last but one node has more values', () => { + let target = { 'a': { 'b': { 'c': 1, 'd': 1 } } }; + + removeFromValueTree(target, 'a.b.c'); + + assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); + }); }); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts new file mode 100644 index 00000000000..598b7d6d616 --- /dev/null +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -0,0 +1,513 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ConfigurationModel, CustomConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import URI from 'vs/base/common/uri'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; + +suite('ConfigurationModel', () => { + + test('setValue for a key that has no sections and not defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b']); + + testObject.setValue('f', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'f']); + }); + + test('setValue for a key that has no sections and defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('f', 3); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'f': 3 }); + assert.deepEqual(testObject.keys, ['a.b', 'f']); + }); + + test('setValue for a key that has sections and not defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('b.c', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'b': { 'c': 1 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'f', 'b.c']); + }); + + test('setValue for a key that has sections and defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'b': { 'c': 1 }, 'f': 1 }, ['a.b', 'b.c', 'f']); + + testObject.setValue('b.c', 3); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1 }, 'b': { 'c': 3 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'b.c', 'f']); + }); + + test('setValue for a key that has sections and sub section not defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('a.c', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1, 'c': 1 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'f', 'a.c']); + }); + + test('setValue for a key that has sections and sub section defined', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1, 'c': 1 }, 'f': 1 }, ['a.b', 'a.c', 'f']); + + testObject.setValue('a.c', 3); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 1, 'c': 3 }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b', 'a.c', 'f']); + }); + + test('setValue for a key that has sections and last section is added', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': {} }, 'f': 1 }, ['a.b', 'f']); + + testObject.setValue('a.b.c', 1); + + assert.deepEqual(testObject.contents, { 'a': { 'b': { 'c': 1 } }, 'f': 1 }); + assert.deepEqual(testObject.keys, ['a.b.c', 'f']); + }); + + test('removeValue: remove a non existing key', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b']); + + testObject.removeValue('a.b.c'); + + assert.deepEqual(testObject.contents, { 'a': { 'b': 2 } }); + assert.deepEqual(testObject.keys, ['a.b']); + }); + + test('removeValue: remove a single segemented key', () => { + let testObject = new ConfigurationModel({ 'a': 1 }, ['a']); + + testObject.removeValue('a'); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + }); + + test('removeValue: remove a multi segemented key', () => { + let testObject = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b']); + + testObject.removeValue('a.b'); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + }); + + test('setValueInOverrides adds to overrides if does not exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a']); + + testObject.setValueInOverrides('or', 'a', 2); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': 2 }); + assert.deepEqual(testObject.override('or').contents, { 'a': 2, 'b': 1 }); + }); + + test('setValueInOverrides adds to overrides if exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or'], contents: { 'a': 2 } }]); + + testObject.setValueInOverrides('or', 'a', 3); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': 3 }); + assert.deepEqual(testObject.override('or').contents, { 'a': 3, 'b': 1 }); + }); + + test('setValueInOverrides adds a nested key to overrides if exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or'], contents: { 'a': { 'c': 1 } } }]); + + testObject.setValueInOverrides('or', 'a.c', 2); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': { 'c': 2 } }); + assert.deepEqual(testObject.override('or').contents, { 'a': { 'c': 2 }, 'b': 1 }); + }); + + test('setValueInOverrides adds new overrides if exist', () => { + let testObject = new ConfigurationModel({ 'a': 1, 'b': 1 }, ['a'], [{ identifiers: ['or1'], contents: { 'a': 2 } }]); + + testObject.setValueInOverrides('or2', 'b', 2); + + assert.deepEqual(testObject.overrides[0].contents, { 'a': 2 }); + assert.deepEqual(testObject.overrides[1].contents, { 'b': 2 }); + assert.deepEqual(testObject.override('or1').contents, { 'a': 2, 'b': 1 }); + assert.deepEqual(testObject.override('or2').contents, { 'a': 1, 'b': 2 }); + }); + + test('get overriding configuration model for an existing identifier', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); + }); + + test('get overriding configuration model for an identifier that does not exist', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + + assert.deepEqual(testObject.override('xyz').contents, { 'a': { 'b': 1 }, 'f': 1 }); + }); + + test('get overriding configuration when one of the keys does not exist in base', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'g': 1 } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1, 'g': 1 }); + }); + + test('get overriding configuration when one of the key in base is not of object type', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': 1 }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': { 'g': 1 } } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': { 'g': 1 } }); + }); + + test('get overriding configuration when one of the key in overriding contents is not of object type', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': 1 } }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); + }); + + test('get overriding configuration if the value of overriding identifier is not object', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], + [{ identifiers: ['c'], contents: 'abc' }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); + }); + + test('get overriding configuration if the value of overriding identifier is an empty object', () => { + let testObject = new ConfigurationModel( + { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], + [{ identifiers: ['c'], contents: {} }]); + + assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); + }); + + test('simple merge', () => { + let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); + let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); + }); + + test('recursive merge', () => { + let base = new ConfigurationModel({ 'a': { 'b': 1 } }); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); + assert.deepEqual(result.getSectionContents('a'), { 'b': 2 }); + }); + + test('simple merge overrides', () => { + let base = new ConfigurationModel({ 'a': { 'b': 1 } }, [], [{ identifiers: ['c'], contents: { 'a': 2 } }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'b': 2 } }]); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); + assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 } }]); + assert.deepEqual(result.override('c').contents, { 'a': 2, 'b': 2 }); + }); + + test('recursive merge overrides', () => { + let base = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, [], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, [], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]); + let result = base.merge(add); + + assert.deepEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); + assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]); + assert.deepEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); + }); + + test('merge ignore keys', () => { + let base = new ConfigurationModel({ 'a': 1, 'b': 2 }); + let add = new ConfigurationModel({ 'a': 3, 'c': 4 }); + let result = base.merge(add); + assert.deepEqual(result.keys, []); + }); + + test('Test contents while getting an existing property', () => { + let testObject = new ConfigurationModel({ 'a': 1 }); + assert.deepEqual(testObject.getSectionContents('a'), 1); + + testObject = new ConfigurationModel({ 'a': { 'b': 1 } }); + assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); + }); + + test('Test contents are undefined for non existing properties', () => { + const testObject = new ConfigurationModel({ awesome: true }); + + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + }); + + test('Test override gives all content merged with overrides', () => { + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 } }]); + + assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); + }); +}); + +suite('CustomConfigurationModel', () => { + + suiteSetup(() => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'a', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'a': { + 'description': 'a', + 'type': 'boolean', + 'default': true, + 'overridable': true + } + } + }); + }); + + test('simple merge using models', () => { + let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); + let add = new CustomConfigurationModel(JSON.stringify({ 'a': 3, 'c': 4 })); + let result = base.merge(add); + assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); + }); + + test('simple merge with an undefined contents', () => { + let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); + let add = new CustomConfigurationModel(null); + let result = base.merge(add); + assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); + + base = new CustomConfigurationModel(null); + add = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); + result = base.merge(add); + assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); + + base = new CustomConfigurationModel(null); + add = new CustomConfigurationModel(null); + result = base.merge(add); + assert.deepEqual(result.contents, {}); + }); + + test('Recursive merge using config models', () => { + let base = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); + let add = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 2 } })); + let result = base.merge(add); + assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); + }); + + test('Test contents while getting an existing property', () => { + let testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1 })); + assert.deepEqual(testObject.getSectionContents('a'), 1); + + testObject = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); + assert.deepEqual(testObject.getSectionContents('a'), { 'b': 1 }); + }); + + test('Test contents are undefined for non existing properties', () => { + const testObject = new CustomConfigurationModel(JSON.stringify({ + awesome: true + })); + + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + }); + + test('Test contents are undefined for undefined config', () => { + const testObject = new CustomConfigurationModel(null); + + assert.deepEqual(testObject.getSectionContents('unknownproperty'), undefined); + }); + + test('Test configWithOverrides gives all content merged with overrides', () => { + const testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); + + assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); + }); + + test('Test configWithOverrides gives empty contents', () => { + const testObject = new CustomConfigurationModel(null); + + assert.deepEqual(testObject.override('b').contents, {}); + }); + + test('Test update with empty data', () => { + const testObject = new CustomConfigurationModel(); + testObject.update(''); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + + testObject.update(null); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + + testObject.update(undefined); + + assert.deepEqual(testObject.contents, {}); + assert.deepEqual(testObject.keys, []); + }); + + test('Test registering the same property again', () => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'a', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'a': { + 'description': 'a', + 'type': 'boolean', + 'default': false, + } + } + }); + assert.equal(true, new DefaultConfigurationModel().getSectionContents('a')); + }); + + test('Test registering the language property', () => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': '[a]', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + '[a]': { + 'description': 'a', + 'type': 'boolean', + 'default': false, + } + } + }); + assert.equal(undefined, new DefaultConfigurationModel().getSectionContents('[a]')); + }); + +}); + +suite('ConfigurationChangeEvent', () => { + + test('changeEvent affecting keys for all resources', () => { + let testObject = new ConfigurationChangeEvent(); + + testObject.change(['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); + + assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files.exclude')); + assert.ok(testObject.affectsConfiguration('[markdown]')); + }); + + test('changeEvent affecting keys for resources', () => { + let testObject = new ConfigurationChangeEvent(); + + testObject.change(['window.title']); + testObject.change(['window.zoomLevel'], URI.file('file1')); + testObject.change(['workbench.editor.enablePreview'], URI.file('file2')); + testObject.change(['window.restoreFullscreen'], URI.file('file1')); + testObject.change(['window.restoreWindows'], URI.file('file2')); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file1'))); + + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1'))); + + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('file1'))); + + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('workbench', URI.file('file1'))); + + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file('file2'))); + }); + +}); + +suite('AllKeysConfigurationChangeEvent', () => { + + test('changeEvent affects keys for any resource', () => { + let testObject = new AllKeysConfigurationChangeEvent(['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows'], ConfigurationTarget.USER, null); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file1'))); + + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1'))); + + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file1'))); + + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('file1'))); + + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); + }); +}); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/model.test.ts b/src/vs/platform/configuration/test/common/model.test.ts deleted file mode 100644 index aa9597b6702..00000000000 --- a/src/vs/platform/configuration/test/common/model.test.ts +++ /dev/null @@ -1,148 +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 * as assert from 'assert'; -import { CustomConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/model'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; - -suite('Configuration', () => { - - suiteSetup(() => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'a', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - 'a': { - 'description': 'a', - 'type': 'boolean', - 'default': true, - 'overridable': true - } - } - }); - }); - - test('simple merge using models', () => { - let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); - let add = new CustomConfigurationModel(JSON.stringify({ 'a': 3, 'c': 4 })); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); - }); - - test('simple merge with an undefined contents', () => { - let base = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); - let add = new CustomConfigurationModel(null); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); - - base = new CustomConfigurationModel(null); - add = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'b': 2 })); - result = base.merge(add); - assert.deepEqual(result.contents, { 'a': 1, 'b': 2 }); - - base = new CustomConfigurationModel(null); - add = new CustomConfigurationModel(null); - result = base.merge(add); - assert.deepEqual(result.contents, {}); - }); - - test('Recursive merge using config models', () => { - let base = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); - let add = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 2 } })); - let result = base.merge(add); - assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - }); - - test('Test contents while getting an existing property', () => { - let testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1 })); - assert.deepEqual(testObject.getContentsFor('a'), 1); - - testObject = new CustomConfigurationModel(JSON.stringify({ 'a': { 'b': 1 } })); - assert.deepEqual(testObject.getContentsFor('a'), { 'b': 1 }); - }); - - test('Test contents are undefined for non existing properties', () => { - const testObject = new CustomConfigurationModel(JSON.stringify({ - awesome: true - })); - - assert.deepEqual(testObject.getContentsFor('unknownproperty'), undefined); - }); - - test('Test contents are undefined for undefined config', () => { - const testObject = new CustomConfigurationModel(null); - - assert.deepEqual(testObject.getContentsFor('unknownproperty'), undefined); - }); - - test('Test configWithOverrides gives all content merged with overrides', () => { - const testObject = new CustomConfigurationModel(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); - - assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); - }); - - test('Test configWithOverrides gives empty contents', () => { - const testObject = new CustomConfigurationModel(null); - - assert.deepEqual(testObject.override('b').contents, {}); - }); - - test('Test update with empty data', () => { - const testObject = new CustomConfigurationModel(); - testObject.update(''); - - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); - - testObject.update(null); - - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); - - testObject.update(undefined); - - assert.deepEqual(testObject.contents, {}); - assert.deepEqual(testObject.keys, []); - }); - - test('Test registering the same property again', () => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'a', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - 'a': { - 'description': 'a', - 'type': 'boolean', - 'default': false, - } - } - }); - assert.equal(true, new DefaultConfigurationModel().getContentsFor('a')); - }); - - test('Test registering the language property', () => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': '[a]', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - '[a]': { - 'description': 'a', - 'type': 'boolean', - 'default': false, - } - } - }); - assert.equal(undefined, new DefaultConfigurationModel().getContentsFor('[a]')); - }); - -}); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index aed4574d678..dcb88e2eb1d 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -9,8 +9,7 @@ import { TernarySearchTree } from 'vs/base/common/map'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { EventEmitter } from 'vs/base/common/eventEmitter'; -import { getConfigurationKeys } from 'vs/platform/configuration/common/model'; -import { IConfigurationOverrides, IConfigurationService, getConfigurationValue, IConfigurationValue, IConfigurationKeys, IConfigurationValues, IConfigurationData, Configuration, ConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; export class TestConfigurationService extends EventEmitter implements IConfigurationService { public _serviceBrand: any; @@ -19,11 +18,12 @@ export class TestConfigurationService extends EventEmitter implements IConfigura private configurationByRoot: TernarySearchTree = TernarySearchTree.forPaths(); - public reloadConfiguration(section?: string): TPromise { + public reloadConfiguration(): TPromise { return TPromise.as(this.getConfiguration()); } - public getConfiguration(section?: string, overrides?: IConfigurationOverrides): any { + public getConfiguration(arg1?: any, arg2?: any): C { + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; if (overrides && overrides.resource) { const configForResource = this.configurationByRoot.findSubstr(overrides.resource.fsPath); return configForResource || this.configuration; @@ -32,8 +32,12 @@ export class TestConfigurationService extends EventEmitter implements IConfigura return this.configuration; } - public getConfigurationData(): IConfigurationData { - return new Configuration(new ConfigurationModel(), new ConfigurationModel(this.configuration)).toData(); + public getValue(key: string, overrides?: IConfigurationOverrides): any { + return this.inspect(key).value; + } + + public updateValue(key: string, overrides?: IConfigurationOverrides): TPromise { + return TPromise.as(null); } public setUserConfiguration(key: any, value: any, root?: URI): Thenable { @@ -48,32 +52,38 @@ export class TestConfigurationService extends EventEmitter implements IConfigura return TPromise.as(null); } - public onDidUpdateConfiguration() { + public onDidChangeConfiguration() { return { dispose() { } }; } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { + public inspect(key: string, overrides?: IConfigurationOverrides): { + default: T, + user: T, + workspace: T, + workspaceFolder: T + value: T, + } { const config = this.getConfiguration(undefined, overrides); return { - value: getConfigurationValue(config, key), - default: getConfigurationValue(config, key), - user: getConfigurationValue(config, key), + value: getConfigurationValue(config, key), + default: getConfigurationValue(config, key), + user: getConfigurationValue(config, key), workspace: null, - folder: null + workspaceFolder: null }; } - public keys(): IConfigurationKeys { + public keys() { return { default: getConfigurationKeys(), user: Object.keys(this.configuration), workspace: [], - folder: [] + workspaceFolder: [] }; } - public values(): IConfigurationValues { - return {}; + public getConfigurationData() { + return null; } } diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 594a6eb5287..60652d4b614 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -122,7 +122,8 @@ suite('ConfigurationService - Node', () => { assert.equal(config.foo, 'bar'); // force a reload to get latest - service.reloadConfiguration<{ foo: string }>().then(config => { + service.reloadConfiguration().then(() => { + config = service.getConfiguration<{ foo: string }>(); assert.ok(config); assert.equal(config.foo, 'changed'); @@ -202,12 +203,12 @@ suite('ConfigurationService - Node', () => { testFile((testFile, cleanUp) => { const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile)); - let res = service.lookup('something.missing'); + let res = service.inspect('something.missing'); assert.strictEqual(res.value, void 0); assert.strictEqual(res.default, void 0); assert.strictEqual(res.user, void 0); - res = service.lookup('lookup.service.testSetting'); + res = service.inspect('lookup.service.testSetting'); assert.strictEqual(res.default, 'isSet'); assert.strictEqual(res.value, 'isSet'); assert.strictEqual(res.user, void 0); @@ -215,7 +216,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(testFile, '{ "lookup.service.testSetting": "bar" }'); return service.reloadConfiguration().then(() => { - res = service.lookup('lookup.service.testSetting'); + res = service.inspect('lookup.service.testSetting'); assert.strictEqual(res.default, 'isSet'); assert.strictEqual(res.user, 'bar'); assert.strictEqual(res.value, 'bar'); @@ -242,7 +243,7 @@ suite('ConfigurationService - Node', () => { testFile((testFile, cleanUp) => { const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile)); - let res = service.lookup('lookup.service.testNullSetting'); + let res = service.inspect('lookup.service.testNullSetting'); assert.strictEqual(res.default, null); assert.strictEqual(res.value, null); assert.strictEqual(res.user, void 0); @@ -250,7 +251,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(testFile, '{ "lookup.service.testNullSetting": null }'); return service.reloadConfiguration().then(() => { - res = service.lookup('lookup.service.testNullSetting'); + res = service.inspect('lookup.service.testNullSetting'); assert.strictEqual(res.default, null); assert.strictEqual(res.value, null); assert.strictEqual(res.user, null); diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index df96f3c0c0d..f12ad44deeb 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -8,7 +8,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { IContextKey, IContext, IContextKeyServiceTarget, IContextKeyService, SET_CONTEXT_COMMAND_ID, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import Event, { Emitter, debounceEvent } from 'vs/base/common/event'; const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context'; @@ -51,22 +51,42 @@ export class Context implements IContext { class ConfigAwareContextValuesContainer extends Context { - private _emitter: Emitter; - private _subscription: IDisposable; + private readonly _emitter: Emitter; + private readonly _subscription: IDisposable; + private readonly _configurationService: IConfigurationService; constructor(id: number, configurationService: IConfigurationService, emitter: Emitter) { super(id, null); this._emitter = emitter; - this._subscription = configurationService.onDidUpdateConfiguration(e => this._updateConfigurationContext(configurationService.getConfiguration())); - this._updateConfigurationContext(configurationService.getConfiguration()); + this._configurationService = configurationService; + this._subscription = configurationService.onDidChangeConfiguration(this._onConfigurationUpdated, this); + this._initFromConfiguration(); } public dispose() { this._subscription.dispose(); } - private _updateConfigurationContext(config: any) { + private _onConfigurationUpdated(event: IConfigurationChangeEvent): void { + if (event.source === ConfigurationTarget.DEFAULT) { + // new setting, rebuild everything + this._initFromConfiguration(); + } else { + // update those that we know + for (const configKey of event.affectedKeys) { + const contextKey = `config.${configKey}`; + if (contextKey in this._value) { + this._value[contextKey] = this._configurationService.getValue(configKey); + this._emitter.fire(configKey); + } + } + } + } + + private _initFromConfiguration() { + + const config = this._configurationService.getConfiguration(); // remove old config.xyz values for (let key in this._value) { diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index b82511d8c71..fa7450947ca 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -9,7 +9,6 @@ import * as assert from 'assert'; import { firstIndex } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { ParsedArgs } from '../common/environment'; -import product from 'vs/platform/node/product'; const options: minimist.Opts = { string: [ @@ -147,11 +146,6 @@ export const optionsHelp: { [name: string]: string; } = { '-h, --help': localize('help', "Print usage.") }; -// TODO@Ben multi root -if (product.quality === 'stable') { - delete optionsHelp['-a, --add']; -} - export function formatOptions(options: { [name: string]: string; }, columns: number): string { let keys = Object.keys(options); let argLength = Math.max.apply(null, keys.map(k => k.length)) + 2/*left padding*/ + 1/*right padding*/; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 7ee13df86d6..4453efbc64d 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -528,7 +528,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { for (let index = 0; index < result.galleryExtensions.length; index++) { const rawExtension = result.galleryExtensions[index]; if (ids.indexOf(rawExtension.extensionId) === -1) { - dependencies.push(toExtension(rawExtension, this.extensionsGalleryUrl, index, query)); + dependencies.push(toExtension(rawExtension, this.extensionsGalleryUrl, index, query, 'dependencies')); ids.push(rawExtension.extensionId); } } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 5b3bdfe9265..58c2a514205 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -268,7 +268,7 @@ export class ExtensionManagementService implements IExtensionManagementService { const promises = installed .filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name) .map(e => this.checkForDependenciesAndUninstall(e, installed, force)); - return TPromise.join(promises).then(null, errors => TPromise.wrapError(this.joinErrors(errors))); + return TPromise.join(promises).then(null, error => TPromise.wrapError(Array.isArray(error) ? this.joinErrors(error) : error)); })) .then(() => { /* drop resolved value */ }); } diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 8e8519b730f..129df5fabce 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -154,15 +154,6 @@ export class HistoryMainService implements IHistoryMainService { files.unshift(...currentFiles.map(f => f.filePath)); } - // TODO@Ben migration to new workspace ID - workspaces.forEach(workspaceOrFile => { - if (isSingleFolderWorkspaceIdentifier(workspaceOrFile)) { - return; - } - - workspaceOrFile.id = this.workspacesService.getWorkspaceId(workspaceOrFile.configPath); - }); - // Clear those dupes workspaces = arrays.distinct(workspaces, workspace => this.distinctFn(workspace)); files = arrays.distinct(files, file => this.distinctFn(file)); diff --git a/src/vs/platform/quickOpen/common/quickOpen.ts b/src/vs/platform/quickOpen/common/quickOpen.ts index c0611d3dde4..0b3bf170855 100644 --- a/src/vs/platform/quickOpen/common/quickOpen.ts +++ b/src/vs/platform/quickOpen/common/quickOpen.ts @@ -123,6 +123,7 @@ export interface IInputOptions { export interface IShowOptions { quickNavigateConfiguration?: IQuickNavigateConfiguration; inputSelection?: { start: number; end: number; }; + autoFocus?: IAutoFocus; } export const IQuickOpenService = createDecorator('quickOpenService'); diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index de5f75fbbd6..a6e58846724 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -29,7 +29,7 @@ export class RequestService implements IRequestService { @IConfigurationService configurationService: IConfigurationService ) { this.configure(configurationService.getConfiguration()); - configurationService.onDidUpdateConfiguration(() => this.configure(configurationService.getConfiguration()), this, this.disposables); + configurationService.onDidChangeConfiguration(() => this.configure(configurationService.getConfiguration()), this, this.disposables); } private configure(config: IHTTPConfiguration) { diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index bb2a8df08e4..bdd1918a876 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -44,6 +44,12 @@ export interface ICommonQueryOptions { filePattern?: string; // file search only fileEncoding?: string; maxResults?: number; + /** + * If true no results will be returned. Instead `limitHit` will indicate if at least one result exists or not. + * + * Currently does not work with queries including a 'siblings clause'. + */ + exists?: boolean; sortByScore?: boolean; cacheKey?: string; useRipgrep?: boolean; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 0ebdb46ff38..45c89aa945a 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -64,7 +64,7 @@ export class TelemetryService implements ITelemetryService { if (this._configurationService) { this._updateUserOptIn(); - this._configurationService.onDidUpdateConfiguration(this._updateUserOptIn, this, this._disposables); + this._configurationService.onDidChangeConfiguration(this._updateUserOptIn, this, this._disposables); /* __GDPR__ "optInStatus" : { "optIn" : { "classification": "SystemMetaData", "purpose": "BusinessInsight" } diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index a910df65983..904d9602127 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { guessMimeTypes } from 'vs/base/common/mime'; import paths = require('vs/base/common/paths'); import URI from 'vs/base/common/uri'; -import { ConfigurationSource, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; @@ -39,33 +39,6 @@ export function combinedAppender(...appenders: ITelemetryAppender[]): ITelemetry export const NullAppender: ITelemetryAppender = { log: () => null }; -// --- util - -export function anonymize(input: string): string { - if (!input) { - return input; - } - - let r = ''; - for (let i = 0; i < input.length; i++) { - let ch = input[i]; - if (ch >= '0' && ch <= '9') { - r += '0'; - continue; - } - if (ch >= 'a' && ch <= 'z') { - r += 'a'; - continue; - } - if (ch >= 'A' && ch <= 'Z') { - r += 'A'; - continue; - } - r += ch; - } - return r; -} - /* __GDPR__FRAGMENT__ "URIDescriptor" : { "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -79,9 +52,9 @@ export interface URIDescriptor { path?: string; } -export function telemetryURIDescriptor(uri: URI): URIDescriptor { +export function telemetryURIDescriptor(uri: URI, hashPath: (path: string) => string): URIDescriptor { const fsPath = uri && uri.fsPath; - return fsPath ? { mimeType: guessMimeTypes(fsPath).join(', '), ext: paths.extname(fsPath), path: anonymize(fsPath) } : {}; + return fsPath ? { mimeType: guessMimeTypes(fsPath).join(', '), ext: paths.extname(fsPath), path: hashPath(fsPath) } : {}; } /** @@ -185,8 +158,8 @@ const configurationValueWhitelist = [ ]; export function configurationTelemetry(telemetryService: ITelemetryService, configurationService: IConfigurationService): IDisposable { - return configurationService.onDidUpdateConfiguration(event => { - if (event.source !== ConfigurationSource.Default) { + return configurationService.onDidChangeConfiguration(event => { + if (event.source !== ConfigurationTarget.DEFAULT) { /* __GDPR__ "updateConfiguration" : { "configurationSource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -194,7 +167,7 @@ export function configurationTelemetry(telemetryService: ITelemetryService, conf } */ telemetryService.publicLog('updateConfiguration', { - configurationSource: ConfigurationSource[event.source], + configurationSource: ConfigurationTarget[event.source], configurationKeys: flattenKeys(event.sourceConfig) }); /* __GDPR__ @@ -204,7 +177,7 @@ export function configurationTelemetry(telemetryService: ITelemetryService, conf } */ telemetryService.publicLog('updateConfigurationValues', { - configurationSource: ConfigurationSource[event.source], + configurationSource: ConfigurationTarget[event.source], configurationValues: flattenValues(event.sourceConfig, configurationValueWhitelist) }); } diff --git a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts index 494159a5f56..69d2f789bb6 100644 --- a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts @@ -681,24 +681,25 @@ suite('TelemetryService', () => { enableTelemetry: enableTelemetry } as any; }, - getConfigurationData(): any { + getValue(key) { + return getConfigurationValue(this.getConfiguration(), key); + }, + updateValue() { return null; }, - reloadConfiguration() { - return TPromise.as(this.getConfiguration()); - }, - lookup(key: string) { + inspect(key: string) { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: null, - folder: null + workspaceFolder: null }; }, - keys() { return { default: [], user: [], workspace: [], folder: [] }; }, - values() { return {}; }, - onDidUpdateConfiguration: emitter.event + keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; }, + onDidChangeConfiguration: emitter.event, + reloadConfiguration() { return null; }, + getConfigurationData() { return null; } }); assert.equal(service.isOptedIn, false); diff --git a/src/vs/platform/theme/common/colorExtensionPoint.ts b/src/vs/platform/theme/common/colorExtensionPoint.ts index 51a47fdb4bb..d9e077db578 100644 --- a/src/vs/platform/theme/common/colorExtensionPoint.ts +++ b/src/vs/platform/theme/common/colorExtensionPoint.ts @@ -42,7 +42,7 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint; } + /** + * Represents a color in RGBA space. + */ + export class Color { + + /** + * The red component of this color in the range [0-1]. + */ + readonly red: number; + + /** + * The green component of this color in the range [0-1]. + */ + readonly green: number; + + /** + * The blue component of this color in the range [0-1]. + */ + readonly blue: number; + + /** + * The alpha component of this color in the range [0-1]. + */ + readonly alpha: number; + + /** + * Creates a new color instance. + * + * @param red The red component. + * @param green The green component. + * @param blue The bluew component. + * @param alpha The alpha component. + */ + constructor(red: number, green: number, blue: number, alpha: number); + } + + /** + * Represents a color range from a document. + */ + export class ColorInformation { + + /** + * The range in the document where this color appers. + */ + range: Range; + + /** + * The actual color value for this color range. + */ + color: Color; + + /** + * Creates a new color range. + * + * @param range The range the color appears in. Must not be empty. + * @param color The value of the color. + * @param format The format in which this color is currently formatted. + */ + constructor(range: Range, color: Color); + } + + /** + * A color presentation object describes how a [`color`](#Color) should be represented as text and what + * edits are required to refer to it from source code. + * + * For some languages one color can have multiple presentations, e.g. css can represent the color red with + * the constant `Red`, the hex-value `#ff0000`, or in rgba and hsla forms. In csharp other representations + * apply, e.g `System.Drawing.Color.Red`. + */ + export class ColorPresentation { + + /** + * The label of this color presentation. It will be shown on the color + * picker header. By default this is also the text that is inserted when selecting + * this color presentation. + */ + label: string; + + /** + * An [edit](#TextEdit) which is applied to a document when selecting + * this presentation for the color. When `falsy` the [label](#ColorPresentation.label) + * is used. + */ + textEdit?: TextEdit; + + /** + * An optional array of additional [text edits](#TextEdit) that are applied when + * selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves. + */ + additionalTextEdits?: TextEdit[]; + + /** + * Creates a new color presentation. + * + * @param label The label of this color presentation. + */ + constructor(label: string); + } + + /** + * The document color provider defines the contract between extensions and feature of + * picking and modifying colors in the editor. + */ + export interface DocumentColorProvider { + + /** + * Provide colors for the given document. + * + * @param document The document in which the command was invoked. + * @param token A cancellation token. + * @return An array of [color informations](#ColorInformation) or a thenable that resolves to such. The lack of a result + * can be signaled by returning `undefined`, `null`, or an empty array. + */ + provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult; + + /** + * Provide [representations](#ColorPresentation) for a color. + * + * @param color The color to show and insert. + * @param context A context object with additional information + * @param token A cancellation token. + * @return An array of color presentations or a thenable that resolves to such. The lack of a result + * can be signaled by returning `undefined`, `null`, or an empty array. + */ + provideColorPresentations(color: Color, context: { document: TextDocument, range: Range }, token: CancellationToken): ProviderResult; + } + /** * A tuple of two characters, like a pair of * opening and closing brackets. @@ -4586,6 +4729,15 @@ declare module 'vscode' { */ export function showQuickPick(items: T[] | Thenable, options?: QuickPickOptions, token?: CancellationToken): Thenable; + /** + * Shows a selection list of [workspace folders](#workspace.workspaceFolders) to pick from. + * Returns `undefined` if no folder is open. + * + * @param options Configures the behavior of the workspace folder list. + * @return A promise that resolves to the workspace folder or `undefined`. + */ + export function showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable; + /** * Shows a file open dialog to the user which allows to select a file * for opening-purposes. @@ -5560,6 +5712,19 @@ declare module 'vscode' { */ export function registerDocumentLinkProvider(selector: DocumentSelector, provider: DocumentLinkProvider): Disposable; + /** + * Register a color provider. + * + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A color provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerColorProvider(selector: DocumentSelector, provider: DocumentColorProvider): Disposable; + /** * Set a [language configuration](#LanguageConfiguration) for a language. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 85ca4a72cd1..3ff70ed91db 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -7,35 +7,6 @@ declare module 'vscode' { - export namespace window { - - /** - * Shows a selection list of [workspace folders](#workspace.workspaceFolders) to pick from. - * Returns `undefined` if no folder is open. - * - * @param options Configures the behavior of the workspace folder list. - * @return A promise that resolves to the workspace folder or `undefined`. - */ - export function showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable; - } - - /** - * Options to configure the behaviour of the [workspace folder](#WorkspaceFolder) pick UI. - */ - export interface WorkspaceFolderPickOptions { - - /** - * An optional string to show as place holder in the input box to guide the user what to pick on. - */ - placeHolder?: string; - - /** - * Set to `true` to keep the picker open when focus moves to another part of the editor or to another window. - */ - ignoreFocusOut?: boolean; - } - - // export enum FileErrorCodes { // /** // * Not owner. @@ -155,6 +126,9 @@ declare module 'vscode' { // todo@remote // create(resource: Uri): Thenable; + + // find files by names + findFiles?(query: string, progress: Progress, token: CancellationToken): Thenable; } export namespace workspace { @@ -195,107 +169,25 @@ declare module 'vscode' { export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable; } - /** - * Represents a color in RGBA space. - */ - export class Color { + //#region decorations - /** - * The red component of this color in the range [0-1]. - */ - readonly red: number; - - /** - * The green component of this color in the range [0-1]. - */ - readonly green: number; - - /** - * The blue component of this color in the range [0-1]. - */ - readonly blue: number; - - /** - * The alpha component of this color in the range [0-1]. - */ - readonly alpha: number; - - constructor(red: number, green: number, blue: number, alpha: number); + //todo@joh -> make class + export interface DecorationData { + priority?: number; + title?: string; + bubble?: boolean; + abbreviation?: string; + color?: ThemeColor; } - /** - * Represents a color range from a document. - */ - export class ColorInformation { - - /** - * The range in the document where this color appers. - */ - range: Range; - - /** - * The actual color value for this color range. - */ - color: Color; - - /** - * Creates a new color range. - * - * @param range The range the color appears in. Must not be empty. - * @param color The value of the color. - * @param format The format in which this color is currently formatted. - */ - constructor(range: Range, color: Color); + export interface DecorationProvider { + onDidChangeDecorations: Event; + provideDecoration(uri: Uri, token: CancellationToken): ProviderResult; } - export class ColorPresentation { - /** - * The label of this color presentation. It will be shown on the color - * picker header. By default this is also the text that is inserted when selecting - * this color presentation. - */ - label: string; - /** - * An [edit](#TextEdit) which is applied to a document when selecting - * this presentation for the color. When `falsy` the [label](#ColorPresentation.label) - * is used. - */ - textEdit?: TextEdit; - /** - * An optional array of additional [text edits](#TextEdit) that are applied when - * selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves. - */ - additionalTextEdits?: TextEdit[]; - - /** - * Creates a new color presentation. - * - * @param label The label of this color presentation. - */ - constructor(label: string); + export namespace window { + export function registerDecorationProvider(provider: DecorationProvider): Disposable; } - /** - * The document color provider defines the contract between extensions and feature of - * picking and modifying colors in the editor. - */ - export interface DocumentColorProvider { - /** - * Provide colors for the given document. - * - * @param document The document in which the command was invoked. - * @param token A cancellation token. - * @return An array of [color informations](#ColorInformation) or a thenable that resolves to such. The lack of a result - * can be signaled by returning `undefined`, `null`, or an empty array. - */ - provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult; - /** - * Provide representations for a color. - */ - provideColorPresentations(document: TextDocument, colorInfo: ColorInformation, token: CancellationToken): ProviderResult; - } - - export namespace languages { - export function registerColorProvider(selector: DocumentSelector, provider: DocumentColorProvider): Disposable; - } + //#endregion } diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index be065d33308..2940cda2e51 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -19,6 +19,7 @@ import './mainThreadCommands'; import './mainThreadConfiguration'; import './mainThreadCredentials'; import './mainThreadDebugService'; +import './mainThreadDecorations'; import './mainThreadDiagnostics'; import './mainThreadDialogs'; import './mainThreadDocumentContentProviders'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts index 71f54113cfd..5133d7125a9 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts @@ -11,9 +11,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext } from '../node/extHost.protocol'; +import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IWorkspaceConfigurationChangeEventData } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @extHostNamedCustomer(MainContext.MainThreadConfiguration) export class MainThreadConfiguration implements MainThreadConfigurationShape { @@ -22,14 +22,13 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { constructor( extHostContext: IExtHostContext, - @IConfigurationEditingService private readonly _configurationEditingService: IConfigurationEditingService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, - @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService + @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService ) { const proxy = extHostContext.get(ExtHostContext.ExtHostConfiguration); - this._configurationListener = configurationService.onDidUpdateConfiguration(() => { - proxy.$acceptConfigurationChanged(configurationService.getConfigurationData()); + this._configurationListener = configurationService.onDidChangeConfiguration(e => { + proxy.$acceptConfigurationChanged(configurationService.getConfigurationData(), this.toConfigurationChangeEventData(e)); }); } @@ -47,16 +46,26 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { private writeConfiguration(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise { target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource); - return this._configurationEditingService.writeConfiguration(target, { key, value }, { donotNotifyError: true, scopes: { resource } }); + return this.configurationService.updateValue(key, value, { resource }, target, true); } private deriveConfigurationTarget(key: string, resource: URI): ConfigurationTarget { if (resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) { - return ConfigurationTarget.FOLDER; + return ConfigurationTarget.WORKSPACE_FOLDER; } } return ConfigurationTarget.WORKSPACE; } + + private toConfigurationChangeEventData(event: IConfigurationChangeEvent): IWorkspaceConfigurationChangeEventData { + return { + changedConfiguration: event.changedConfiguration, + changedConfigurationByResource: event.changedConfigurationByResource.keys().reduce((result, resource) => { + result[resource.toString()] = event.changedConfigurationByResource.get(resource); + return result; + }, Object.create({})) + }; + } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index 3776bf439c0..af2d72759b5 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -80,22 +80,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { }); } - public $startDebugSession(folderUri: uri | undefined, configuration: IConfig): TPromise { - if (configuration.request !== 'launch' && configuration.request !== 'attach') { - return TPromise.wrapError(new Error(`only 'launch' or 'attach' allowed for 'request' attribute`)); - } - - const folder = folderUri ? this.contextService.getWorkspace().folders.filter(wf => wf.uri.toString() === folderUri.toString()).pop() : undefined; - return this.debugService.createProcess(folder, configuration).then(process => { - if (process) { - return process.getId(); - } - return TPromise.wrapError(new Error('cannot create debug session')); - }, err => { - return TPromise.wrapError(err && err.message ? err.message : 'cannot start debug session'); - }); - } - public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: any): TPromise { const process = this.debugService.findProcessByUUID(sessionId); if (process) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts new file mode 100644 index 00000000000..7ac2b8c92ef --- /dev/null +++ b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * 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 URI from 'vs/base/common/uri'; +import { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape } from '../node/extHost.protocol'; +import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; + +@extHostNamedCustomer(MainContext.MainThreadDecorations) +export class MainThreadDecorations implements MainThreadDecorationsShape { + + private readonly _provider = new Map, IDisposable]>(); + private readonly _proxy: ExtHostDecorationsShape; + + constructor( + context: IExtHostContext, + @IDecorationsService private readonly _decorationsService: IDecorationsService + ) { + this._proxy = context.get(ExtHostContext.ExtHostDecorations); + } + + dispose() { + this._provider.forEach(value => dispose(value)); + this._provider.clear(); + } + + $registerDecorationProvider(handle: number): void { + let emitter = new Emitter(); + let registration = this._decorationsService.registerDecorationsProvider({ + label: 'extension-provider', + onDidChange: emitter.event, + provideDecorations: (uri) => { + return this._proxy.$providerDecorations(handle, uri).then(data => { + if (!data) { + return undefined; + } + const [weight, bubble, title, letter, themeColor] = data; + return { + weight: weight || 0, + bubble: bubble || false, + title, + letter, + color: themeColor && themeColor.id + }; + }); + } + }); + this._provider.set(handle, [emitter, registration]); + } + + $onDidChange(handle: number, resources: URI[]): void { + const [emitter] = this._provider.get(handle); + emitter.fire(resources); + } + + $unregisterDecorationProvider(handle: number): void { + if (this._provider.has(handle)) { + dispose(this._provider.get(handle)); + this._provider.delete(handle); + } + } +} diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts index 40d2943f7e6..df3174ae67c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts @@ -243,7 +243,7 @@ export class MainThreadEditors implements MainThreadEditorsShape { const edit = edits[j]; resourceEdits.push({ - resource: uri, + resource: uri, newText: edit.newText, newEol: edit.newEol, range: edit.range diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts index 76aac923806..5b6ea8b6f3a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -5,7 +5,7 @@ 'use strict'; import URI from 'vs/base/common/uri'; -import { TPromise } from 'vs/base/common/winjs.base'; +import { TPromise, PPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, MainContext, IExtHostContext, MainThreadFileSystemShape, ExtHostFileSystemShape } from '../node/extHost.protocol'; import { IFileService, IFileSystemProvider, IStat, IFileChange } from 'vs/platform/files/common/files'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -13,6 +13,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IProgress } from 'vs/platform/progress/common/progress'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { ISearchResultProvider, ISearchQuery, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchService } from 'vs/platform/search/common/search'; @extHostNamedCustomer(MainContext.MainThreadFileSystem) export class MainThreadFileSystem implements MainThreadFileSystemShape { @@ -24,6 +25,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { constructor( extHostContext: IExtHostContext, @IFileService private readonly _fileService: IFileService, + @ISearchService private readonly _searchService: ISearchService, @IWorkspaceEditingService private readonly _workspaceEditService: IWorkspaceEditingService ) { this._proxy = extHostContext.get(ExtHostContext.ExtHostFileSystem); @@ -34,7 +36,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } $registerFileSystemProvider(handle: number, scheme: string): void { - this._provider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, handle, this._proxy)); + this._provider.set(handle, new RemoteFileSystemProvider(this._fileService, this._searchService, scheme, handle, this._proxy)); } $unregisterFileSystemProvider(handle: number): void { @@ -53,28 +55,38 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { $reportFileChunk(handle: number, resource: URI, chunk: number[]): void { this._provider.get(handle).reportFileChunk(resource, chunk); } + + // --- search + + $handleSearchProgress(handle: number, session: number, resource: URI): void { + this._provider.get(handle).handleSearchProgress(session, resource); + } } -class RemoteFileSystemProvider implements IFileSystemProvider { +class RemoteFileSystemProvider implements IFileSystemProvider, ISearchResultProvider { private readonly _onDidChange = new Emitter(); - private readonly _registration: IDisposable; private readonly _reads = new Map>(); + private readonly _registrations: IDisposable[]; readonly onDidChange: Event = this._onDidChange.event; constructor( - service: IFileService, + fileService: IFileService, + searchService: ISearchService, scheme: string, private readonly _handle: number, private readonly _proxy: ExtHostFileSystemShape ) { - this._registration = service.registerProvider(scheme, this); + this._registrations = [ + fileService.registerProvider(scheme, this), + searchService.registerSearchResultProvider(this), + ]; } dispose(): void { - this._registration.dispose(); + dispose(this._registrations); this._onDidChange.dispose(); } @@ -115,4 +127,36 @@ class RemoteFileSystemProvider implements IFileSystemProvider { rmdir(resource: URI): TPromise { return this._proxy.$rmdir(this._handle, resource); } + + // --- search + + private _searches = new Map void>(); + private _searchesIdPool = 0; + + search(query: ISearchQuery): PPromise { + if (query.type === QueryType.Text) { + return PPromise.as({ results: [], stats: undefined }); + } + const id = ++this._searchesIdPool; + const matches: IFileMatch[] = []; + return new PPromise((resolve, reject, report) => { + this._proxy.$fileFiles(this._handle, id, query.filePattern).then(() => { + this._searches.delete(id); + resolve({ + results: matches, + stats: undefined + }); + }, reject); + + this._searches.set(id, resource => { + const match: IFileMatch = { resource }; + matches.push(match); + report(match); + }); + }); + } + + handleSearchProgress(session: number, resource: URI): void { + this._searches.get(session)(resource); + } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts index 777ec4918af..d46aff79813 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts @@ -41,7 +41,7 @@ class TrimWhitespaceParticipant implements INamedSaveParticpant { } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void { - if (this.configurationService.lookup('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) { + if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } } @@ -99,7 +99,7 @@ export class FinalNewLineParticipant implements INamedSaveParticpant { } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void { - if (this.configurationService.lookup('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) { + if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doInsertFinalNewLine(model.textEditorModel); } } @@ -139,7 +139,7 @@ export class TrimFinalNewLinesParticipant implements INamedSaveParticpant { } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void { - if (this.configurationService.lookup('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) { + if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimFinalNewLines(model.textEditorModel); } } @@ -189,7 +189,7 @@ class FormatOnSaveParticipant implements INamedSaveParticpant { const model = editorModel.textEditorModel; if (env.reason === SaveReason.AUTO - || !this._configurationService.lookup('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }).value) { + || !this._configurationService.getValue('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() })) { return undefined; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 69d00f909db..69e09705743 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -68,7 +68,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } const useRipgrep = folderQueries.every(folderQuery => { - const folderConfig = this._configurationService.getConfiguration(undefined, { resource: folderQuery.folder }); + const folderConfig = this._configurationService.getConfiguration({ resource: folderQuery.folder }); return folderConfig.search.useRipgrep; }); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index b5423526507..128eb4a907e 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -55,6 +55,7 @@ import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadServi import { ExtHostDialogs } from 'vs/workbench/api/node/extHostDialogs'; import { ExtHostFileSystem } from 'vs/workbench/api/node/extHostFileSystem'; import { FileChangeType, FileType } from 'vs/platform/files/common/files'; +import { ExtHostDecorations } from 'vs/workbench/api/node/extHostDecorations'; export interface IExtensionApiFactory { (extension: IExtensionDescription): typeof vscode; @@ -81,10 +82,9 @@ export function createApiFactory( extensionService: ExtHostExtensionService ): IExtensionApiFactory { - const mainThreadTelemetry = threadService.get(MainContext.MainThreadTelemetry); - // Addressable instances const extHostHeapService = threadService.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService()); + const extHostDecorations = threadService.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(threadService)); const extHostDocumentsAndEditors = threadService.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(threadService)); const extHostDocuments = threadService.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(threadService, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = threadService.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(threadService, extHostDocumentsAndEditors)); @@ -140,29 +140,6 @@ export function createApiFactory( } } - const apiUsage = new class { - private _seen = new Set(); - publicLog(apiName: string) { - if (this._seen.has(apiName)) { - return undefined; - } - this._seen.add(apiName); - /* __GDPR__ - "apiUsage" : { - "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extension": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "${include}": [ - "${MainThreadData}" - ] - } - */ - return mainThreadTelemetry.$publicLog('apiUsage', { - name: apiName, - extension: extension.id - }); - } - }; - // namespace: commands const commands: typeof vscode.commands = { registerCommand(id: string, command: (...args: any[]) => T | Thenable, thisArgs?: any): vscode.Disposable { @@ -294,13 +271,12 @@ export function createApiFactory( registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { return languageFeatures.registerDocumentLinkProvider(selector, provider); }, + registerColorProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable { + return languageFeatures.registerColorProvider(selector, provider); + }, setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => { return languageFeatures.setLanguageConfiguration(language, configuration); - }, - // proposed API - registerColorProvider: proposedApiFunction(extension, (selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider) => { - return languageFeatures.registerColorProvider(selector, provider); - }) + } }; // namespace: window @@ -346,9 +322,9 @@ export function createApiFactory( get state() { return extHostWindow.state; }, - onDidChangeWindowState: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { + onDidChangeWindowState(listener, thisArg?, disposables?) { return extHostWindow.onDidChangeWindowState(listener, thisArg, disposables); - }), + }, showInformationMessage(message, first, ...rest) { return extHostMessageService.showMessage(extension, Severity.Info, message, first, rest); }, @@ -361,9 +337,9 @@ export function createApiFactory( showQuickPick(items: any, options: vscode.QuickPickOptions, token?: vscode.CancellationToken) { return extHostQuickOpen.showQuickPick(items, options, token); }, - showWorkspaceFolderPick: proposedApiFunction(extension, (options: vscode.WorkspaceFolderPickOptions) => { + showWorkspaceFolderPick(options: vscode.WorkspaceFolderPickOptions) { return extHostQuickOpen.showWorkspaceFolderPick(options); - }), + }, showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken) { return extHostQuickOpen.showInput(options, token); }, @@ -402,27 +378,26 @@ export function createApiFactory( sampleFunction: proposedApiFunction(extension, () => { return extHostMessageService.showMessage(extension, Severity.Info, 'Hello Proposed Api!', {}, []); }), + registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => { + return extHostDecorations.registerDecorationProvider(provider, extension.id); + }) }; // namespace: workspace const workspace: typeof vscode.workspace = { get rootPath() { - apiUsage.publicLog('workspace#rootPath'); return extHostWorkspace.getPath(); }, set rootPath(value) { throw errors.readonly(); }, getWorkspaceFolder(resource) { - apiUsage.publicLog('workspace#getWorkspaceFolder'); return extHostWorkspace.getWorkspaceFolder(resource); }, get workspaceFolders() { - apiUsage.publicLog('workspace#workspaceFolders'); return extHostWorkspace.getWorkspaceFolders(); }, onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) { - apiUsage.publicLog('workspace#onDidChangeWorkspaceFolders'); return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables); }, asRelativePath: (pathOrUri, includeWorkspace) => { @@ -453,7 +428,7 @@ export function createApiFactory( if (typeof uriOrFileNameOrOptions === 'string') { uriPromise = TPromise.as(URI.file(uriOrFileNameOrOptions)); } else if (uriOrFileNameOrOptions instanceof URI) { - uriPromise = TPromise.as(uriOrFileNameOrOptions); + uriPromise = TPromise.as(uriOrFileNameOrOptions); } else if (!options || typeof options === 'object') { uriPromise = extHostDocuments.createDocumentData(options); } else { @@ -486,7 +461,7 @@ export function createApiFactory( return extHostConfiguration.onDidChangeConfiguration(listener, thisArgs, disposables); }, getConfiguration: (section?: string, resource?: vscode.Uri): vscode.WorkspaceConfiguration => { - return extHostConfiguration.getConfiguration(section, resource); + return extHostConfiguration.getConfiguration(section, resource); }, registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) { return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); @@ -505,22 +480,6 @@ export function createApiFactory( return extHostSCM.getLastInputBox(extension); }, createSourceControl(id: string, label: string, rootUri?: vscode.Uri) { - /* __GDPR__ - "registerSCMProvider" : { - "extensionId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "providerId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, - "providerLabel": { "classification": "PublicPersonalData", "purpose": "FeatureInsight" }, - "${include}": [ - "${MainThreadData}" - ] - } - */ - mainThreadTelemetry.$publicLog('registerSCMProvider', { - extensionId: extension.id, - providerId: id, - providerLabel: label - }); - return extHostSCM.createSourceControl(extension, id, label, rootUri); } }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 4016299feed..a6c002ae30f 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -28,8 +28,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import * as modes from 'vs/editor/common/modes'; import { ITextSource } from 'vs/editor/common/model/textSource'; -import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -74,10 +73,15 @@ export interface IInitData { environment: IEnvironment; workspace: IWorkspaceData; extensions: IExtensionDescription[]; - configuration: IConfigurationData; + configuration: IConfigurationData; telemetryInfo: ITelemetryInfo; } +export interface IWorkspaceConfigurationChangeEventData { + changedConfiguration: IConfigurationModel; + changedConfigurationByResource: { [folder: string]: IConfigurationModel }; +} + export interface IExtHostContext { /** * Returns a proxy to an object addressable/named in the extension host process. @@ -136,6 +140,12 @@ export interface MainThreadDiaglogsShape extends IDisposable { $showSaveDialog(options: MainThreadDialogSaveOptions): TPromise; } +export interface MainThreadDecorationsShape extends IDisposable { + $registerDecorationProvider(handle: number, label: string): void; + $unregisterDecorationProvider(handle: number): void; + $onDidChange(handle: number, resources: URI[]): void; +} + export interface MainThreadDocumentContentProvidersShape extends IDisposable { $registerTextContentProvider(handle: number, scheme: string): void; $unregisterTextContentProvider(handle: number): void; @@ -324,6 +334,8 @@ export interface MainThreadFileSystemShape extends IDisposable { $onDidAddFileSystemRoot(root: URI): void; $onFileSystemChange(handle: number, resource: IFileChange[]): void; $reportFileChunk(handle: number, resource: URI, chunk: number[] | null): void; + + $handleSearchProgress(handle: number, session: number, resource: URI): void; } export interface MainThreadTaskShape extends IDisposable { @@ -390,7 +402,6 @@ export interface MainThreadDebugServiceShape extends IDisposable { $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, handle: number): TPromise; $unregisterDebugConfigurationProvider(handle: number): TPromise; $startDebugging(folder: URI | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise; - $startDebugSession(folder: URI | undefined, config: vscode.DebugConfiguration): TPromise; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): TPromise; } @@ -412,7 +423,7 @@ export interface ExtHostCommandsShape { } export interface ExtHostConfigurationShape { - $acceptConfigurationChanged(data: IConfigurationData): void; + $acceptConfigurationChanged(data: IConfigurationData, eventData: IWorkspaceConfigurationChangeEventData): void; } export interface ExtHostDiagnosticsShape { @@ -489,6 +500,7 @@ export interface ExtHostFileSystemShape { $mkdir(handle: number, resource: URI): TPromise; $readdir(handle: number, resource: URI): TPromise<[URI, IStat][]>; $rmdir(handle: number, resource: URI): TPromise; + $fileFiles(handle: number, session: number, query: string): TPromise; } export interface ExtHostExtensionServiceShape { @@ -594,6 +606,13 @@ export interface ExtHostDebugServiceShape { $acceptDebugSessionCustomEvent(id: DebugSessionUUID, type: string, name: string, event: any): void; } + +export type DecorationData = [number, boolean, string, string, ThemeColor]; + +export interface ExtHostDecorationsShape { + $providerDecorations(handle: number, uri: URI): TPromise; +} + export interface ExtHostCredentialsShape { } @@ -607,6 +626,7 @@ export const MainContext = { MainThreadCommands: createMainId('MainThreadCommands'), MainThreadConfiguration: createMainId('MainThreadConfiguration'), MainThreadDebugService: createMainId('MainThreadDebugService'), + MainThreadDecorations: createMainId('MainThreadDecorations'), MainThreadDiagnostics: createMainId('MainThreadDiagnostics'), MainThreadDialogs: createMainId('MainThreadDiaglogs'), MainThreadDocuments: createMainId('MainThreadDocuments'), @@ -638,6 +658,7 @@ export const ExtHostContext = { ExtHostConfiguration: createExtId('ExtHostConfiguration'), ExtHostDiagnostics: createExtId('ExtHostDiagnostics'), ExtHostDebugService: createExtId('ExtHostDebugService'), + ExtHostDecorations: createExtId('ExtHostDecorations'), ExtHostDocumentsAndEditors: createExtId('ExtHostDocumentsAndEditors'), ExtHostDocuments: createExtId('ExtHostDocuments'), ExtHostDocumentContentProviders: createExtId('ExtHostDocumentContentProviders'), diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 931b01fc875..fde9013ed78 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -7,12 +7,14 @@ import { mixin } from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import Event, { Emitter } from 'vs/base/common/event'; -import { WorkspaceConfiguration } from 'vscode'; +import * as vscode from 'vscode'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; -import { ExtHostConfigurationShape, MainThreadConfigurationShape } from './extHost.protocol'; +import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; -import { IConfigurationData, Configuration } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationData, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { Configuration, ConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; +import { StrictResourceMap } from 'vs/base/common/map'; function lookUp(tree: any, key: string) { if (key) { @@ -38,9 +40,9 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { private readonly _onDidChangeConfiguration = new Emitter(); private readonly _proxy: MainThreadConfigurationShape; private readonly _extHostWorkspace: ExtHostWorkspace; - private _configuration: Configuration; + private _configuration: Configuration; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationData) { + constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationData) { this._proxy = proxy; this._extHostWorkspace = extHostWorkspace; this._configuration = Configuration.parse(data, extHostWorkspace.workspace); @@ -50,15 +52,15 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event; } - $acceptConfigurationChanged(data: IConfigurationData) { + $acceptConfigurationChanged(data: IConfigurationData, eventData: IWorkspaceConfigurationChangeEventData) { this._configuration = Configuration.parse(data, this._extHostWorkspace.workspace); this._onDidChangeConfiguration.fire(undefined); } - getConfiguration(section?: string, resource?: URI): WorkspaceConfiguration { + getConfiguration(section?: string, resource?: URI): vscode.WorkspaceConfiguration { const config = section - ? lookUp(this._configuration.getValue(null, { resource }), section) - : this._configuration.getValue(null, { resource }); + ? lookUp(this._configuration.getSection(null, { resource }), section) + : this._configuration.getSection(null, { resource }); function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget { if (arg === void 0 || arg === null) { @@ -71,11 +73,11 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { switch (arg) { case ExtHostConfigurationTarget.Global: return ConfigurationTarget.USER; case ExtHostConfigurationTarget.Workspace: return ConfigurationTarget.WORKSPACE; - case ExtHostConfigurationTarget.WorkspaceFolder: return ConfigurationTarget.FOLDER; + case ExtHostConfigurationTarget.WorkspaceFolder: return ConfigurationTarget.WORKSPACE_FOLDER; } } - const result: WorkspaceConfiguration = { + const result: vscode.WorkspaceConfiguration = { has(key: string): boolean { return typeof lookUp(config, key) !== 'undefined'; }, @@ -104,7 +106,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { defaultValue: config.default, globalValue: config.user, workspaceValue: config.workspace, - workspaceFolderValue: config.folder + workspaceFolderValue: config.workspaceFolder }; } return undefined; @@ -115,6 +117,18 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { mixin(result, config, false); } - return Object.freeze(result); + return Object.freeze(result); + } + + protected toConfigurationChangeEvent(data: IWorkspaceConfigurationChangeEventData): WorkspaceConfigurationChangeEvent { + const changedConfiguration = new ConfigurationModel(data.changedConfiguration.contents, data.changedConfiguration.keys, data.changedConfiguration.overrides); + const changedConfigurationByResource: StrictResourceMap = new StrictResourceMap(); + for (const key of Object.keys(data.changedConfigurationByResource)) { + const resource = URI.parse(key); + const model = data.changedConfigurationByResource[key]; + changedConfigurationByResource.set(resource, new ConfigurationModel(model.contents, model.keys, model.overrides)); + } + const event = new ConfigurationChangeEvent(changedConfiguration, changedConfigurationByResource); + return new WorkspaceConfigurationChangeEvent(event, this._extHostWorkspace.workspace); } } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 1e7a6c0d3df..71727e711d5 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -95,15 +95,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise { - return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig); - } - - public startDebugSession(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration): TPromise { - return this._debugServiceProxy.$startDebugSession(folder ? folder.uri : undefined, config).then((id: DebugSessionUUID) => { - const debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, config.type, config.name); - this._debugSessions.set(id, debugSession); - return debugSession; - }); + return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig); } public $acceptDebugSessionStarted(id: DebugSessionUUID, type: string, name: string): void { diff --git a/src/vs/workbench/api/node/extHostDecorations.ts b/src/vs/workbench/api/node/extHostDecorations.ts new file mode 100644 index 00000000000..e327dbe4995 --- /dev/null +++ b/src/vs/workbench/api/node/extHostDecorations.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * 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 vscode from 'vscode'; +import URI from 'vs/base/common/uri'; +import { MainContext, IMainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData } from 'vs/workbench/api/node/extHost.protocol'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Disposable } from 'vs/workbench/api/node/extHostTypes'; +import { asWinJsPromise } from 'vs/base/common/async'; + +export class ExtHostDecorations implements ExtHostDecorationsShape { + + private static _handlePool = 0; + + private readonly _provider = new Map(); + private readonly _proxy: MainThreadDecorationsShape; + + constructor(mainContext: IMainContext) { + this._proxy = mainContext.get(MainContext.MainThreadDecorations); + } + + registerDecorationProvider(provider: vscode.DecorationProvider, label: string): vscode.Disposable { + const handle = ExtHostDecorations._handlePool++; + this._provider.set(handle, provider); + this._proxy.$registerDecorationProvider(handle, label); + + const listener = provider.onDidChangeDecorations(e => { + this._proxy.$onDidChange(handle, !e ? null : Array.isArray(e) ? e : [e]); + }); + + return new Disposable(() => { + listener.dispose(); + this._proxy.$unregisterDecorationProvider(handle); + this._provider.delete(handle); + }); + } + + $providerDecorations(handle: number, uri: URI): TPromise { + const provider = this._provider.get(handle); + return asWinJsPromise(token => provider.provideDecoration(uri, token)).then(data => { + return data && [data.priority, data.bubble, data.title, data.abbreviation, data.color]; + }); + } +} diff --git a/src/vs/workbench/api/node/extHostDiagnostics.ts b/src/vs/workbench/api/node/extHostDiagnostics.ts index 1a200433765..8b44277e384 100644 --- a/src/vs/workbench/api/node/extHostDiagnostics.ts +++ b/src/vs/workbench/api/node/extHostDiagnostics.ts @@ -133,7 +133,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { } } - entries.push([uri, marker]); + entries.push([uri, marker]); } this._proxy.$changeMany(this.name, entries); @@ -142,7 +142,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { delete(uri: vscode.Uri): void { this._checkDisposed(); this._data.delete(uri.toString()); - this._proxy.$changeMany(this.name, [[uri, undefined]]); + this._proxy.$changeMany(this.name, [[uri, undefined]]); } clear(): void { diff --git a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts index c3d6e39536e..dc53c36fcc8 100644 --- a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts @@ -47,7 +47,7 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro if (typeof provider.onDidChange === 'function') { subscription = provider.onDidChange(uri => { if (this._documentsAndEditors.getDocument(uri.toString())) { - this.$provideTextDocumentContent(handle, uri).then(value => { + this.$provideTextDocumentContent(handle, uri).then(value => { const document = this._documentsAndEditors.getDocument(uri.toString()); if (!document) { @@ -60,7 +60,7 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro // broadcast event when content changed if (!document.equalLines(textSource)) { - return this._proxy.$onVirtualDocumentChange(uri, textSource); + return this._proxy.$onVirtualDocumentChange(uri, textSource); } }, onUnexpectedError); diff --git a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts index 3be0250cf70..16d2ed87ccd 100644 --- a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts @@ -129,7 +129,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic }).then(values => { let workspaceResourceEdit: IWorkspaceResourceEdit = { - resource: document.uri, + resource: document.uri, edits: [] }; diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index 6b7466381f5..a7b8c787310 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -74,4 +74,12 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { $rmdir(handle: number, resource: URI): TPromise { return asWinJsPromise(token => this._provider.get(handle).rmdir(resource)); } + $fileFiles(handle: number, session: number, query: string): TPromise { + const provider = this._provider.get(handle); + if (!provider.findFiles) { + return TPromise.as(undefined); + } + const progress = { report: (uri) => this._proxy.$handleSearchProgress(handle, session, uri) }; + return asWinJsPromise(token => provider.findFiles(query, progress, token)); + } } diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 1b9211584d8..7718b667dd4 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { mixin } from 'vs/base/common/objects'; import * as vscode from 'vscode'; import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString } from 'vs/workbench/api/node/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, Color } from 'vs/workbench/api/node/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/editorCommon'; import * as modes from 'vs/editor/common/modes'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; @@ -18,7 +18,7 @@ import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHos import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { IWorkspaceSymbolProvider } from 'vs/workbench/parts/search/common/search'; import { asWinJsPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadTelemetryShape, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IExtHostSuggestResult, IExtHostSuggestion } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IExtHostSuggestResult, IExtHostSuggestion } from './extHost.protocol'; import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; @@ -437,7 +437,7 @@ class RenameAdapter { let [uri, textEdits] = entry; for (let textEdit of textEdits) { result.edits.push({ - resource: uri, + resource: uri, newText: textEdit.newText, range: TypeConverters.fromRange(textEdit.range) }); @@ -721,19 +721,12 @@ class ColorProviderAdapter { }); } - provideColorPresentations(resource: URI, rawColorInfo: IRawColorInfo): TPromise { - let colorInfo: vscode.ColorInformation = { - range: TypeConverters.toRange(rawColorInfo.range), - color: { - red: rawColorInfo.color[0], - green: rawColorInfo.color[1], - blue: rawColorInfo.color[2], - alpha: rawColorInfo.color[3] - } - }; - const doc = this._documents.getDocumentData(resource).document; - return asWinJsPromise(token => this._provider.provideColorPresentations(doc, colorInfo, token)).then(value => { - return value.map(v => TypeConverters.ColorPresentation.from(v)); + provideColorPresentations(resource: URI, raw: IRawColorInfo): TPromise { + const document = this._documents.getDocumentData(resource).document; + const range = TypeConverters.toRange(raw.range); + const color = new Color(raw.color[0], raw.color[1], raw.color[2], raw.color[3]); + return asWinJsPromise(token => this._provider.provideColorPresentations(color, { document, range }, token)).then(value => { + return value.map(TypeConverters.ColorPresentation.from); }); } } @@ -748,7 +741,6 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { private static _handlePool: number = 0; private _proxy: MainThreadLanguageFeaturesShape; - private _telemetry: MainThreadTelemetryShape; private _documents: ExtHostDocuments; private _commands: ExtHostCommands; private _heapService: ExtHostHeapService; @@ -764,7 +756,6 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { diagnostics: ExtHostDiagnostics ) { this._proxy = mainContext.get(MainContext.MainThreadLanguageFeatures); - this._telemetry = mainContext.get(MainContext.MainThreadTelemetry); this._documents = documents; this._commands = commands; this._heapService = heapMonitor; diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index ec9ad8b38ee..310ea752f91 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -204,7 +204,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG return; } - this._commands.executeCommand(command.command, ...command.arguments); + await this._commands.executeCommand(command.command, ...command.arguments); } _takeResourceStateSnapshot(): SCMRawResourceSplice[] { @@ -531,6 +531,6 @@ export class ExtHostSCM { return; } - group.$executeResourceCommand(handle); + await group.$executeResourceCommand(handle); } } diff --git a/src/vs/workbench/api/node/extHostTextEditors.ts b/src/vs/workbench/api/node/extHostTextEditors.ts index d1f70271a48..d3ca9b8edc8 100644 --- a/src/vs/workbench/api/node/extHostTextEditors.ts +++ b/src/vs/workbench/api/node/extHostTextEditors.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; import Event, { Emitter } from 'vs/base/common/event'; import { toThenable } from 'vs/base/common/async'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -77,7 +76,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { }; } - return this._proxy.$tryShowTextDocument(document.uri, options).then(id => { + return this._proxy.$tryShowTextDocument(document.uri, options).then(id => { let editor = this._extHostDocumentsAndEditors.getEditor(id); if (editor) { return editor; @@ -106,7 +105,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { } let workspaceResourceEdit: IWorkspaceResourceEdit = { - resource: uri, + resource: uri, modelVersionId: docVersion, edits: [] }; diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 47010f07757..54c80ba84c4 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -284,7 +284,7 @@ export const location = { from(value: vscode.Location): modes.Location { return { range: value.range && fromRange(value.range), - uri: value.uri + uri: value.uri }; }, to(value: modes.Location): types.Location { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index c0044aa87a9..616ce83c24c 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -794,7 +794,7 @@ export class SymbolInformation { if (locationOrUri instanceof Location) { this.location = locationOrUri; } else if (rangeOrContainer instanceof Range) { - this.location = new Location(locationOrUri, rangeOrContainer); + this.location = new Location(locationOrUri, rangeOrContainer); } } diff --git a/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts b/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts index bd78ed75df5..9ae30a6d59b 100644 --- a/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; export class ToggleActivityBarVisibilityAction extends Action { @@ -24,7 +24,7 @@ export class ToggleActivityBarVisibilityAction extends Action { id: string, label: string, @IPartService private partService: IPartService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IConfigurationService private configurationService: IConfigurationService ) { super(id, label); @@ -35,9 +35,7 @@ export class ToggleActivityBarVisibilityAction extends Action { const visibility = this.partService.isVisible(Parts.ACTIVITYBAR_PART); const newVisibilityValue = !visibility; - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleActivityBarVisibilityAction.activityBarVisibleKey, value: newVisibilityValue }); - - return TPromise.as(null); + return this.configurationService.updateValue(ToggleActivityBarVisibilityAction.activityBarVisibleKey, newVisibilityValue, ConfigurationTarget.USER); } } diff --git a/src/vs/workbench/browser/actions/toggleSidebarPosition.ts b/src/vs/workbench/browser/actions/toggleSidebarPosition.ts index 6e62c166319..fc94e997424 100644 --- a/src/vs/workbench/browser/actions/toggleSidebarPosition.ts +++ b/src/vs/workbench/browser/actions/toggleSidebarPosition.ts @@ -10,8 +10,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IPartService, Position } from 'vs/workbench/services/part/common/partService'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export class ToggleSidebarPositionAction extends Action { @@ -24,20 +24,18 @@ export class ToggleSidebarPositionAction extends Action { id: string, label: string, @IPartService private partService: IPartService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IConfigurationService private configurationService: IConfigurationService ) { super(id, label); - this.enabled = !!this.partService && !!this.configurationEditingService; + this.enabled = !!this.partService && !!this.configurationService; } public run(): TPromise { const position = this.partService.getSideBarPosition(); const newPositionValue = (position === Position.LEFT) ? 'right' : 'left'; - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleSidebarPositionAction.sidebarPositionConfigurationKey, value: newPositionValue }); - - return TPromise.as(null); + return this.configurationService.updateValue(ToggleSidebarPositionAction.sidebarPositionConfigurationKey, newPositionValue, ConfigurationTarget.USER); } } diff --git a/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts b/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts index ddaadd0e277..5949ea28756 100644 --- a/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; export class ToggleStatusbarVisibilityAction extends Action { @@ -24,7 +24,7 @@ export class ToggleStatusbarVisibilityAction extends Action { id: string, label: string, @IPartService private partService: IPartService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IConfigurationService private configurationService: IConfigurationService ) { super(id, label); @@ -35,9 +35,7 @@ export class ToggleStatusbarVisibilityAction extends Action { const visibility = this.partService.isVisible(Parts.STATUSBAR_PART); const newVisibilityValue = !visibility; - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleStatusbarVisibilityAction.statusbarVisibleKey, value: newVisibilityValue }); - - return TPromise.as(null); + return this.configurationService.updateValue(ToggleStatusbarVisibilityAction.statusbarVisibleKey, newVisibilityValue, ConfigurationTarget.USER); } } diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 93a7498c27b..5c0a40f1752 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -20,9 +20,11 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IDecorationsService, IResourceDecorationChangeEvent } from 'vs/workbench/services/decorations/browser/decorations'; import { Schemas } from 'vs/base/common/network'; import { FileKind } from 'vs/platform/files/common/files'; import { IModel } from 'vs/editor/common/editorCommon'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; export interface IResourceLabel { name: string; @@ -32,6 +34,7 @@ export interface IResourceLabel { export interface IResourceLabelOptions extends IIconLabelOptions { fileKind?: FileKind; + fileDecorations?: { colors: boolean, badges: boolean }; } export class ResourceLabel extends IconLabel { @@ -49,7 +52,9 @@ export class ResourceLabel extends IconLabel { @IConfigurationService private configurationService: IConfigurationService, @IModeService private modeService: IModeService, @IModelService private modelService: IModelService, - @IEnvironmentService protected environmentService: IEnvironmentService + @IEnvironmentService protected environmentService: IEnvironmentService, + @IDecorationsService protected decorationsService: IDecorationsService, + @IThemeService private themeService: IThemeService ) { super(container, options); @@ -60,8 +65,10 @@ export class ResourceLabel extends IconLabel { private registerListeners(): void { this.extensionService.onReady().then(() => this.render(true /* clear cache */)); // update when extensions are loaded with potentially new languages - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(() => this.render(true /* clear cache */))); // update when file.associations change + this.toDispose.push(this.configurationService.onDidChangeConfiguration(() => this.render(true /* clear cache */))); // update when file.associations change this.toDispose.push(this.modelService.onModelModeChanged(e => this.onModelModeChanged(e))); // react to model mode changes + this.toDispose.push(this.decorationsService.onDidChangeDecorations(this.onFileDecorationsChanges, this)); // react to file decoration changes + this.toDispose.push(this.themeService.onThemeChange(() => this.render(false))); } private onModelModeChanged(e: { model: IModel; oldModeId: string; }): void { @@ -84,6 +91,15 @@ export class ResourceLabel extends IconLabel { } } + private onFileDecorationsChanges(e: IResourceDecorationChangeEvent): void { + if (!this.options || !this.label || !this.label.resource) { + return; + } + if (this.options.fileDecorations && e.affectsResource(this.label.resource)) { + this.render(false); + } + } + public setLabel(label: IResourceLabel, options?: IResourceLabelOptions): void { const hasResourceChanged = this.hasResourceChanged(label, options); @@ -141,28 +157,48 @@ export class ResourceLabel extends IconLabel { return; } + const iconLabelOptions: IIconLabelOptions = { + title: '', + italic: this.options && this.options.italic, + matches: this.options && this.options.matches, + }; + const resource = this.label.resource; + let label = this.label.name; + - let title = ''; if (this.options && typeof this.options.title === 'string') { - title = this.options.title; + iconLabelOptions.title = this.options.title; } else if (resource) { - title = getPathLabel(resource, void 0, this.environmentService); + iconLabelOptions.title = getPathLabel(resource, void 0, this.environmentService); } if (!this.computedIconClasses) { this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options && this.options.fileKind); } - let extraClasses = this.computedIconClasses.slice(0); + iconLabelOptions.extraClasses = this.computedIconClasses.slice(0); if (this.options && this.options.extraClasses) { - extraClasses.push(...this.options.extraClasses); + iconLabelOptions.extraClasses.push(...this.options.extraClasses); } - const italic = this.options && this.options.italic; - const matches = this.options && this.options.matches; + if (this.options && this.options.fileDecorations) { + let deco = this.decorationsService.getDecoration( + resource, + this.options.fileKind !== FileKind.FILE + ); + if (deco && this.options.fileDecorations.colors) { + iconLabelOptions.extraClasses.push(deco.labelClassName); + } + if (deco && deco.badgeClassName && this.options.fileDecorations.badges) { + iconLabelOptions.badge = { + title: deco.title, + className: deco.badgeClassName, + }; + } + } - this.setValue(this.label.name, this.label.description, { title, extraClasses, italic, matches }); + this.setValue(label, this.label.description, iconLabelOptions); } public dispose(): void { @@ -204,9 +240,11 @@ export class FileLabel extends ResourceLabel { @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IEnvironmentService environmentService: IEnvironmentService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService + @IDecorationsService decorationsService: IDecorationsService, + @IThemeService themeService: IThemeService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, ) { - super(container, options, extensionService, contextService, configurationService, modeService, modelService, environmentService); + super(container, options, extensionService, contextService, configurationService, modeService, modelService, environmentService, decorationsService, themeService); } public setFile(resource: uri, options?: IFileLabelOptions): void { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 8add806cda5..04c486ec533 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -6,75 +6,21 @@ 'use strict'; import 'vs/css!./media/activityaction'; -import nls = require('vs/nls'); import DOM = require('vs/base/browser/dom'); import { TPromise } from 'vs/base/common/winjs.base'; -import { Builder, $ } from 'vs/base/browser/builder'; -import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { Action } from 'vs/base/common/actions'; -import { BaseActionItem, Separator, IBaseActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IActivityBarService, ProgressBadge, TextBadge, NumberBadge, IconBadge, IBadge } from 'vs/workbench/services/activity/common/activityBarService'; -import Event, { Emitter } from 'vs/base/common/event'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { IActivity, IGlobalActivity } from 'vs/workbench/common/activity'; import { dispose } from 'vs/base/common/lifecycle'; import { IViewletService, } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme'; -import { contrastBorder, activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; - -export interface IViewletActivity { - badge: IBadge; - clazz: string; -} - -export class ActivityAction extends Action { - private badge: IBadge; - private _onDidChangeBadge = new Emitter(); - - constructor(private _activity: IActivity) { - super(_activity.id, _activity.name, _activity.cssClass); - - this.badge = null; - } - - public get activity(): IActivity { - return this._activity; - } - - public get onDidChangeBadge(): Event { - return this._onDidChangeBadge.event; - } - - public activate(): void { - if (!this.checked) { - this._setChecked(true); - } - } - - public deactivate(): void { - if (this.checked) { - this._setChecked(false); - } - } - - public getBadge(): IBadge { - return this.badge; - } - - public setBadge(badge: IBadge): void { - this.badge = badge; - this._onDidChangeBadge.fire(this); - } -} +import { ActivityAction, ActivityActionItem } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; export class ViewletActivityAction extends ActivityAction { @@ -83,15 +29,11 @@ export class ViewletActivityAction extends ActivityAction { private lastRun: number = 0; constructor( - private viewlet: ViewletDescriptor, + activity: IActivity, @IViewletService private viewletService: IViewletService, @IPartService private partService: IPartService ) { - super(viewlet); - } - - public get descriptor(): ViewletDescriptor { - return this.viewlet; + super(activity); } public run(event: any): TPromise { @@ -110,495 +52,15 @@ export class ViewletActivityAction extends ActivityAction { const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible - if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) { + if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) { return this.partService.setSideBarHidden(true); } - return this.viewletService.openViewlet(this.viewlet.id, true).then(() => this.activate()); + return this.viewletService.openViewlet(this.activity.id, true).then(() => this.activate()); } } -export class ActivityActionItem extends BaseActionItem { - protected $container: Builder; - protected $label: Builder; - protected $badge: Builder; - - private $badgeContent: Builder; - private mouseUpTimeout: number; - - constructor( - action: ActivityAction, - options: IBaseActionItemOptions, - @IThemeService protected themeService: IThemeService - ) { - super(null, action, options); - - this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose); - action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose); - } - - protected get activity(): IActivity { - return (this._action as ActivityAction).activity; - } - - protected updateStyles(): void { - const theme = this.themeService.getTheme(); - - // Label - if (this.$label) { - const background = theme.getColor(ACTIVITY_BAR_FOREGROUND); - - this.$label.style('background-color', background ? background.toString() : null); - } - - // Badge - if (this.$badgeContent) { - const badgeForeground = theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND); - const badgeBackground = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND); - const contrastBorderColor = theme.getColor(contrastBorder); - - this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null); - this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null); - - this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null); - this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null); - this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null); - } - } - - public render(container: HTMLElement): void { - super.render(container); - - // Make the container tab-able for keyboard navigation - this.$container = $(container).attr({ - tabIndex: '0', - role: 'button', - title: this.activity.name - }); - - // Try hard to prevent keyboard only focus feedback when using mouse - this.$container.on(DOM.EventType.MOUSE_DOWN, () => { - this.$container.addClass('clicked'); - }); - - this.$container.on(DOM.EventType.MOUSE_UP, () => { - if (this.mouseUpTimeout) { - clearTimeout(this.mouseUpTimeout); - } - - this.mouseUpTimeout = setTimeout(() => { - this.$container.removeClass('clicked'); - }, 800); // delayed to prevent focus feedback from showing on mouse up - }); - - // Label - this.$label = $('a.action-label').appendTo(this.builder); - if (this.activity.cssClass) { - this.$label.addClass(this.activity.cssClass); - } - - this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => { - this.$badgeContent = badge.div({ 'class': 'badge-content' }); - }); - - this.$badge.hide(); - - this.updateStyles(); - } - - private onThemeChange(theme: ITheme): void { - this.updateStyles(); - } - - public setBadge(badge: IBadge): void { - this.updateBadge(badge); - } - - protected updateBadge(badge: IBadge): void { - this.$badgeContent.empty(); - this.$badge.hide(); - - if (badge) { - - // Number - if (badge instanceof NumberBadge) { - if (badge.number) { - this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString()); - this.$badge.show(); - } - } - - // Text - else if (badge instanceof TextBadge) { - this.$badgeContent.text(badge.text); - this.$badge.show(); - } - - // Text - else if (badge instanceof IconBadge) { - this.$badge.show(); - } - - // Progress - else if (badge instanceof ProgressBadge) { - this.$badge.show(); - } - } - - // Title - let title: string; - if (badge && badge.getDescription()) { - if (this.activity.name) { - title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription()); - } else { - title = badge.getDescription(); - } - } else { - title = this.activity.name; - } - - [this.$label, this.$badge, this.$container].forEach(b => { - if (b) { - b.attr('aria-label', title); - b.title(title); - } - }); - } - - private handleBadgeChangeEvenet(): void { - const action = this.getAction(); - if (action instanceof ActivityAction) { - this.updateBadge(action.getBadge()); - } - } - - public dispose(): void { - super.dispose(); - - if (this.mouseUpTimeout) { - clearTimeout(this.mouseUpTimeout); - } - - this.$badge.destroy(); - } -} - -export class ViewletActionItem extends ActivityActionItem { - - private static manageExtensionAction: ManageExtensionAction; - private static toggleViewletPinnedAction: ToggleViewletPinnedAction; - private static draggedViewlet: ViewletDescriptor; - - private viewletActivity: IActivity; - private cssClass: string; - - constructor( - private action: ViewletActivityAction, - @IContextMenuService private contextMenuService: IContextMenuService, - @IActivityBarService private activityBarService: IActivityBarService, - @IKeybindingService private keybindingService: IKeybindingService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService - ) { - super(action, { draggable: true }, themeService); - - this.cssClass = action.class; - - if (!ViewletActionItem.manageExtensionAction) { - ViewletActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); - } - - if (!ViewletActionItem.toggleViewletPinnedAction) { - ViewletActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0); - } - } - - protected get activity(): IActivity { - if (!this.viewletActivity) { - let activityName: string; - - const keybinding = this.getKeybindingLabel(this.viewlet.id); - if (keybinding) { - activityName = nls.localize('titleKeybinding', "{0} ({1})", this.viewlet.name, keybinding); - } else { - activityName = this.viewlet.name; - } - - this.viewletActivity = { - id: this.viewlet.id, - cssClass: this.cssClass, - name: activityName - }; - } - - return this.viewletActivity; - } - - private get viewlet(): ViewletDescriptor { - return this.action.descriptor; - } - - private getKeybindingLabel(id: string): string { - const kb = this.keybindingService.lookupKeybinding(id); - if (kb) { - return kb.getLabel(); - } - - return null; - } - - public render(container: HTMLElement): void { - super.render(container); - - this.$container.on('contextmenu', e => { - DOM.EventHelper.stop(e, true); - - this.showContextMenu(container); - }); - - // Allow to drag - this.$container.on(DOM.EventType.DRAG_START, (e: DragEvent) => { - e.dataTransfer.effectAllowed = 'move'; - this.setDraggedViewlet(this.viewlet); - - // Trigger the action even on drag start to prevent clicks from failing that started a drag - if (!this.getAction().checked) { - this.getAction().run(); - } - }); - - // Drag enter - let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470 - this.$container.on(DOM.EventType.DRAG_ENTER, (e: DragEvent) => { - const draggedViewlet = ViewletActionItem.getDraggedViewlet(); - if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) { - counter++; - this.updateFromDragging(container, true); - } - }); - - // Drag leave - this.$container.on(DOM.EventType.DRAG_LEAVE, (e: DragEvent) => { - const draggedViewlet = ViewletActionItem.getDraggedViewlet(); - if (draggedViewlet) { - counter--; - if (counter === 0) { - this.updateFromDragging(container, false); - } - } - }); - - // Drag end - this.$container.on(DOM.EventType.DRAG_END, (e: DragEvent) => { - const draggedViewlet = ViewletActionItem.getDraggedViewlet(); - if (draggedViewlet) { - counter = 0; - this.updateFromDragging(container, false); - - ViewletActionItem.clearDraggedViewlet(); - } - }); - - // Drop - this.$container.on(DOM.EventType.DROP, (e: DragEvent) => { - DOM.EventHelper.stop(e, true); - - const draggedViewlet = ViewletActionItem.getDraggedViewlet(); - if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) { - this.updateFromDragging(container, false); - ViewletActionItem.clearDraggedViewlet(); - - this.activityBarService.move(draggedViewlet.id, this.viewlet.id); - } - }); - - // Activate on drag over to reveal targets - [this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => { - if (!ViewletActionItem.getDraggedViewlet() && !this.getAction().checked) { - this.getAction().run(); - } - })); - - this.updateStyles(); - } - - private updateFromDragging(element: HTMLElement, isDragging: boolean): void { - const theme = this.themeService.getTheme(); - const dragBackground = theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND); - - element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null; - } - - public static getDraggedViewlet(): ViewletDescriptor { - return ViewletActionItem.draggedViewlet; - } - - private setDraggedViewlet(viewlet: ViewletDescriptor): void { - ViewletActionItem.draggedViewlet = viewlet; - } - - public static clearDraggedViewlet(): void { - ViewletActionItem.draggedViewlet = void 0; - } - - private showContextMenu(container: HTMLElement): void { - const actions: Action[] = [ViewletActionItem.toggleViewletPinnedAction]; - if (this.viewlet.extensionId) { - actions.push(new Separator()); - actions.push(ViewletActionItem.manageExtensionAction); - } - - const isPinned = this.activityBarService.isPinned(this.viewlet.id); - if (isPinned) { - ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Hide from Activity Bar"); - } else { - ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar"); - } - - this.contextMenuService.showContextMenu({ - getAnchor: () => container, - getActionsContext: () => this.viewlet, - getActions: () => TPromise.as(actions) - }); - } - - public focus(): void { - this.$container.domFocus(); - } - - protected _updateClass(): void { - if (this.cssClass) { - this.$badge.removeClass(this.cssClass); - } - - this.cssClass = this.getAction().class; - this.$badge.addClass(this.cssClass); - } - - protected _updateChecked(): void { - if (this.getAction().checked) { - this.$container.addClass('checked'); - } else { - this.$container.removeClass('checked'); - } - } - - protected _updateEnabled(): void { - if (this.getAction().enabled) { - this.builder.removeClass('disabled'); - } else { - this.builder.addClass('disabled'); - } - } - - public dispose(): void { - super.dispose(); - - ViewletActionItem.clearDraggedViewlet(); - - this.$label.destroy(); - } -} - -export class ViewletOverflowActivityAction extends ActivityAction { - - constructor( - private showMenu: () => void - ) { - super({ - id: 'activitybar.additionalViewlets.action', - name: nls.localize('additionalViews', "Additional Views"), - cssClass: 'toggle-more' - }); - } - - public run(event: any): TPromise { - this.showMenu(); - - return TPromise.as(true); - } -} - -export class ViewletOverflowActivityActionItem extends ActivityActionItem { - private name: string; - private cssClass: string; - private actions: OpenViewletAction[]; - - constructor( - action: ActivityAction, - private getOverflowingViewlets: () => ViewletDescriptor[], - private getBadge: (viewlet: ViewletDescriptor) => IBadge, - @IInstantiationService private instantiationService: IInstantiationService, - @IViewletService private viewletService: IViewletService, - @IContextMenuService private contextMenuService: IContextMenuService, - @IThemeService themeService: IThemeService - ) { - super(action, null, themeService); - - this.cssClass = action.class; - this.name = action.label; - } - - public showMenu(): void { - if (this.actions) { - dispose(this.actions); - } - - this.actions = this.getActions(); - - this.contextMenuService.showContextMenu({ - getAnchor: () => this.builder.getHTMLElement(), - getActions: () => TPromise.as(this.actions), - onHide: () => dispose(this.actions) - }); - } - - private getActions(): OpenViewletAction[] { - const activeViewlet = this.viewletService.getActiveViewlet(); - - return this.getOverflowingViewlets().map(viewlet => { - const action = this.instantiationService.createInstance(OpenViewletAction, viewlet); - action.radio = activeViewlet && activeViewlet.getId() === action.id; - - const badge = this.getBadge(action.viewlet); - let suffix: string | number; - if (badge instanceof NumberBadge) { - suffix = badge.number; - } else if (badge instanceof TextBadge) { - suffix = badge.text; - } - - if (suffix) { - action.label = nls.localize('numberBadge', "{0} ({1})", action.viewlet.name, suffix); - } else { - action.label = action.viewlet.name; - } - - return action; - }); - } - - public dispose(): void { - super.dispose(); - - this.actions = dispose(this.actions); - } -} - -class ManageExtensionAction extends Action { - - constructor( - @ICommandService private commandService: ICommandService - ) { - super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension")); - } - - public run(viewlet: ViewletDescriptor): TPromise { - return this.commandService.executeCommand('_extensions.manage', viewlet.extensionId); - } -} - -class OpenViewletAction extends Action { +export class ToggleViewletAction extends Action { constructor( private _viewlet: ViewletDescriptor, @@ -608,44 +70,16 @@ class OpenViewletAction extends Action { super(_viewlet.id, _viewlet.name); } - public get viewlet(): ViewletDescriptor { - return this._viewlet; - } - public run(): TPromise { const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART); const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible - if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) { + if (sideBarVisible && activeViewlet && activeViewlet.getId() === this._viewlet.id) { return this.partService.setSideBarHidden(true); } - return this.viewletService.openViewlet(this.viewlet.id, true); - } -} - -export class ToggleViewletPinnedAction extends Action { - - constructor( - private viewlet: ViewletDescriptor, - @IActivityBarService private activityBarService: IActivityBarService - ) { - super('activitybar.show.toggleViewletPinned', viewlet ? viewlet.name : nls.localize('toggle', "Toggle View Pinned")); - - this.checked = this.viewlet && this.activityBarService.isPinned(this.viewlet.id); - } - - public run(context?: ViewletDescriptor): TPromise { - const viewlet = this.viewlet || context; - - if (this.activityBarService.isPinned(viewlet.id)) { - this.activityBarService.unpin(viewlet.id); - } else { - this.activityBarService.pin(viewlet.id); - } - - return TPromise.as(true); + return this.viewletService.openViewlet(this._viewlet.id, true); } } @@ -687,6 +121,12 @@ export class GlobalActivityActionItem extends ActivityActionItem { public onClick(event?: MouseEvent | KeyboardEvent): void { DOM.EventHelper.stop(event, true); + // Prevent duplicate menu showing because we already handle MOUSE_DOWN + // (refs: // https://github.com/Microsoft/vscode/issues/36244) + if (event.type === DOM.EventType.CLICK) { + return; + } + let location: HTMLElement | { x: number, y: number }; if (event instanceof MouseEvent) { const mouseEvent = new StandardMouseEvent(event); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 5afcced6e5c..49b1cb6a684 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -8,25 +8,20 @@ import 'vs/css!./media/activitybarpart'; import nls = require('vs/nls'); import { TPromise } from 'vs/base/common/winjs.base'; -import DOM = require('vs/base/browser/dom'); -import * as arrays from 'vs/base/common/arrays'; import { illegalArgument } from 'vs/base/common/errors'; import { Builder, $, Dimension } from 'vs/base/browser/builder'; import { Action } from 'vs/base/common/actions'; -import { ActionsOrientation, ActionBar, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; +import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; -import { IViewlet } from 'vs/workbench/common/viewlet'; -import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, GlobalActivityActionItem, ViewletActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem, GlobalActivityAction, IViewletActivity } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; +import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IActivityBarService, IBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IPartService, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { Scope as MementoScope } from 'vs/workbench/common/memento'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -34,6 +29,8 @@ import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar'; +import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; export class ActivitybarPart extends Part implements IActivityBarService { @@ -47,17 +44,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActionBar: ActionBar; private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; }; - private viewletSwitcherBar: ActionBar; - private viewletOverflowAction: ViewletOverflowActivityAction; - private viewletOverflowActionItem: ViewletOverflowActivityActionItem; - - private viewletIdToActions: { [viewletId: string]: ActivityAction; }; - private viewletIdToActionItems: { [viewletId: string]: IActionItem; }; - private viewletIdToActivityStack: { [viewletId: string]: IViewletActivity[]; }; - - private memento: object; - private pinnedViewlets: string[]; - private activeUnpinnedViewlet: ViewletDescriptor; + private compositeBar: CompositeBar; constructor( id: string, @@ -72,58 +59,35 @@ export class ActivitybarPart extends Part implements IActivityBarService { super(id, { hasTitle: false }, themeService); this.globalActivityIdToActions = Object.create(null); - - this.viewletIdToActionItems = Object.create(null); - this.viewletIdToActions = Object.create(null); - this.viewletIdToActivityStack = Object.create(null); - - this.memento = this.getMemento(this.storageService, MementoScope.GLOBAL); - - const pinnedViewlets = this.memento[ActivitybarPart.PINNED_VIEWLETS] as string[]; - - if (pinnedViewlets) { - this.pinnedViewlets = pinnedViewlets; - } else { - this.pinnedViewlets = this.viewletService.getViewlets().map(v => v.id); - } - + this.compositeBar = this.instantiationService.createInstance(CompositeBar, { + label: 'icon', + storageId: ActivitybarPart.PINNED_VIEWLETS, + orientation: ActionsOrientation.VERTICAL, + composites: this.viewletService.getViewlets(), + getCompositeSize: (compositeId: string) => ActivitybarPart.ACTIVITY_ACTION_HEIGHT, + openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true), + getActivityAction: (compositeId: string) => this.instantiationService.createInstance(ViewletActivityAction, this.viewletService.getViewlet(compositeId)), + getCompositePinnedAction: (compositeId: string) => new ToggleCompositePinnedAction(this.viewletService.getViewlet(compositeId), this.compositeBar), + getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)), + getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), + hidePart: () => this.partService.setSideBarHidden(true) + }); this.registerListeners(); } private registerListeners(): void { // Activate viewlet action on opening of a viewlet - this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet))); + this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId()))); // Deactivate viewlet action on close - this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.onDidViewletClose(viewlet))); - } - - private onDidViewletOpen(viewlet: IViewlet): void { - const id = viewlet.getId(); - - if (this.viewletIdToActions[id]) { - this.viewletIdToActions[id].activate(); - } - - const activeUnpinnedViewletShouldClose = this.activeUnpinnedViewlet && this.activeUnpinnedViewlet.id !== viewlet.getId(); - const activeUnpinnedViewletShouldShow = !this.getPinnedViewlets().some(v => v.id === viewlet.getId()); - if (activeUnpinnedViewletShouldShow || activeUnpinnedViewletShouldClose) { - this.updateViewletSwitcher(); - } - } - - private onDidViewletClose(viewlet: IViewlet): void { - const id = viewlet.getId(); - - if (this.viewletIdToActions[id]) { - this.viewletIdToActions[id].deactivate(); - } + this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId()))); + this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e))); } public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string): IDisposable { if (this.viewletService.getViewlet(viewletOrActionId)) { - return this.showViewletActivity(viewletOrActionId, badge, clazz); + return this.compositeBar.showActivity(viewletOrActionId, badge, clazz); } return this.showGlobalActivity(viewletOrActionId, badge); @@ -144,93 +108,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { return toDisposable(() => action.setBadge(undefined)); } - private showViewletActivity(viewletId: string, badge: IBadge, clazz?: string): IDisposable { - if (!badge) { - throw illegalArgument('badge'); - } - - const activity = { badge, clazz }; - const stack = this.viewletIdToActivityStack[viewletId] || (this.viewletIdToActivityStack[viewletId] = []); - stack.unshift(activity); - - this.updateViewletActivity(viewletId); - - return { - dispose: () => { - const stack = this.viewletIdToActivityStack[viewletId]; - if (!stack) { - return; - } - - const idx = stack.indexOf(activity); - if (idx < 0) { - return; - } - - stack.splice(idx, 1); - if (stack.length === 0) { - delete this.viewletIdToActivityStack[viewletId]; - } - - this.updateViewletActivity(viewletId); - } - }; - } - - private updateViewletActivity(viewletId: string) { - const action = this.viewletIdToActions[viewletId]; - if (!action) { - return; - } - - const stack = this.viewletIdToActivityStack[viewletId]; - - // reset - if (!stack || !stack.length) { - action.setBadge(undefined); - } - - // update - else { - const [{ badge, clazz }] = stack; - action.setBadge(badge); - if (clazz) { - action.class = clazz; - } - } - } - public createContentArea(parent: Builder): Builder { const $el = $(parent); const $result = $('.content').appendTo($el); // Top Actionbar with action items for each viewlet action - this.createViewletSwitcher($result.clone()); + this.compositeBar.create($result.getHTMLElement()); // Top Actionbar with action items for each viewlet action - this.createGlobalActivityActionBar($result.getHTMLElement()); - - // Contextmenu for viewlets - $(parent).on('contextmenu', (e: MouseEvent) => { - DOM.EventHelper.stop(e, true); - - this.showContextMenu(e); - }, this.toUnbind); - - // Allow to drop at the end to move viewlet to the end - $(parent).on(DOM.EventType.DROP, (e: DragEvent) => { - const draggedViewlet = ViewletActionItem.getDraggedViewlet(); - if (draggedViewlet) { - DOM.EventHelper.stop(e, true); - - ViewletActionItem.clearDraggedViewlet(); - - const targetId = this.pinnedViewlets[this.pinnedViewlets.length - 1]; - if (targetId !== draggedViewlet.id) { - this.move(draggedViewlet.id, this.pinnedViewlets[this.pinnedViewlets.length - 1]); - } - } - }); + this.createGlobalActivityActionBar($('.global-activity').appendTo($result).getHTMLElement()); return $result; } @@ -257,7 +143,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private showContextMenu(e: MouseEvent): void { const event = new StandardMouseEvent(e); - const actions: Action[] = this.viewletService.getViewlets().map(viewlet => this.instantiationService.createInstance(ToggleViewletPinnedAction, viewlet)); + const actions: Action[] = this.viewletService.getViewlets().map(viewlet => this.instantiationService.createInstance(ToggleCompositePinnedAction, viewlet, this.compositeBar)); actions.push(new Separator()); actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))); @@ -268,20 +154,6 @@ export class ActivitybarPart extends Part implements IActivityBarService { }); } - private createViewletSwitcher(div: Builder): void { - this.viewletSwitcherBar = new ActionBar(div, { - actionItemProvider: (action: Action) => action instanceof ViewletOverflowActivityAction ? this.viewletOverflowActionItem : this.viewletIdToActionItems[action.id], - orientation: ActionsOrientation.VERTICAL, - ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"), - animated: false - }); - - this.updateViewletSwitcher(); - - // Update viewlet switcher when external viewlets become ready - this.extensionService.onReady().then(() => this.updateViewletSwitcher()); - } - private createGlobalActivityActionBar(container: HTMLElement): void { const activityRegistry = Registry.as(GlobalActivityExtensions); const descriptors = activityRegistry.getActivities(); @@ -302,234 +174,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { }); } - private updateViewletSwitcher() { - if (!this.viewletSwitcherBar) { - return; // We have not been rendered yet so there is nothing to update. - } - - let viewletsToShow = this.getPinnedViewlets(); - - // Always show the active viewlet even if it is marked to be hidden - const activeViewlet = this.viewletService.getActiveViewlet(); - if (activeViewlet && !viewletsToShow.some(viewlet => viewlet.id === activeViewlet.getId())) { - this.activeUnpinnedViewlet = this.viewletService.getViewlet(activeViewlet.getId()); - viewletsToShow.push(this.activeUnpinnedViewlet); - } else { - this.activeUnpinnedViewlet = void 0; - } - - // Ensure we are not showing more viewlets than we have height for - let overflows = false; - if (this.dimension) { - let availableHeight = this.dimension.height; - if (this.globalActionBar) { - availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTIVITY_ACTION_HEIGHT); // adjust for global actions showing - } - - const maxVisible = Math.floor(availableHeight / ActivitybarPart.ACTIVITY_ACTION_HEIGHT); - overflows = viewletsToShow.length > maxVisible; - - if (overflows) { - viewletsToShow = viewletsToShow.slice(0, maxVisible - 1 /* make room for overflow action */); - } - } - - const visibleViewlets = Object.keys(this.viewletIdToActions); - const visibleViewletsChange = !arrays.equals(viewletsToShow.map(viewlet => viewlet.id), visibleViewlets); - - // Pull out overflow action if there is a viewlet change so that we can add it to the end later - if (this.viewletOverflowAction && visibleViewletsChange) { - this.viewletSwitcherBar.pull(this.viewletSwitcherBar.length() - 1); - - this.viewletOverflowAction.dispose(); - this.viewletOverflowAction = null; - - this.viewletOverflowActionItem.dispose(); - this.viewletOverflowActionItem = null; - } - - // Pull out viewlets that overflow or got hidden - const viewletIdsToShow = viewletsToShow.map(v => v.id); - visibleViewlets.forEach(viewletId => { - if (viewletIdsToShow.indexOf(viewletId) === -1) { - this.pullViewlet(viewletId); - } - }); - - // Built actions for viewlets to show - const newViewletsToShow = viewletsToShow - .filter(viewlet => !this.viewletIdToActions[viewlet.id]) - .map(viewlet => this.toAction(viewlet)); - - // Update when we have new viewlets to show - if (newViewletsToShow.length) { - - // Add to viewlet switcher - this.viewletSwitcherBar.push(newViewletsToShow, { label: true, icon: true }); - - // Make sure to activate the active one - const activeViewlet = this.viewletService.getActiveViewlet(); - if (activeViewlet) { - const activeViewletEntry = this.viewletIdToActions[activeViewlet.getId()]; - if (activeViewletEntry) { - activeViewletEntry.activate(); - } - } - - // Make sure to restore activity - Object.keys(this.viewletIdToActions).forEach(viewletId => { - this.updateViewletActivity(viewletId); - }); - } - - // Add overflow action as needed - if (visibleViewletsChange && overflows) { - this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, () => this.viewletOverflowActionItem.showMenu()); - this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, () => this.getOverflowingViewlets(), (viewlet: ViewletDescriptor) => this.viewletIdToActivityStack[viewlet.id] && this.viewletIdToActivityStack[viewlet.id][0].badge); - - this.viewletSwitcherBar.push(this.viewletOverflowAction, { label: true, icon: true }); - } - } - - private getOverflowingViewlets(): ViewletDescriptor[] { - const viewlets = this.getPinnedViewlets(); - if (this.activeUnpinnedViewlet) { - viewlets.push(this.activeUnpinnedViewlet); - } - const visibleViewlets = Object.keys(this.viewletIdToActions); - - return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) === -1); - } - - private getVisibleViewlets(): ViewletDescriptor[] { - const viewlets = this.viewletService.getViewlets(); - const visibleViewlets = Object.keys(this.viewletIdToActions); - - return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) >= 0); - } - - private getPinnedViewlets(): ViewletDescriptor[] { - return this.pinnedViewlets.map(viewletId => this.viewletService.getViewlet(viewletId)).filter(v => !!v); // ensure to remove those that might no longer exist - } - - private pullViewlet(viewletId: string): void { - const index = Object.keys(this.viewletIdToActions).indexOf(viewletId); - if (index >= 0) { - this.viewletSwitcherBar.pull(index); - - const action = this.viewletIdToActions[viewletId]; - action.dispose(); - delete this.viewletIdToActions[viewletId]; - - const actionItem = this.viewletIdToActionItems[action.id]; - actionItem.dispose(); - delete this.viewletIdToActionItems[action.id]; - } - } - - private toAction(viewlet: ViewletDescriptor): ActivityAction { - const action = this.instantiationService.createInstance(ViewletActivityAction, viewlet); - - this.viewletIdToActionItems[action.id] = this.instantiationService.createInstance(ViewletActionItem, action); - this.viewletIdToActions[viewlet.id] = action; - - return action; - } - public getPinned(): string[] { - return this.pinnedViewlets; - } - - public unpin(viewletId: string): void { - if (!this.isPinned(viewletId)) { - return; - } - - const activeViewlet = this.viewletService.getActiveViewlet(); - const defaultViewletId = this.viewletService.getDefaultViewletId(); - const visibleViewlets = this.getVisibleViewlets(); - - let unpinPromise: TPromise; - - // Case: viewlet is not the active one or the active one is a different one - // Solv: we do nothing - if (!activeViewlet || activeViewlet.getId() !== viewletId) { - unpinPromise = TPromise.as(null); - } - - // Case: viewlet is not the default viewlet and default viewlet is still showing - // Solv: we open the default viewlet - else if (defaultViewletId !== viewletId && this.isPinned(defaultViewletId)) { - unpinPromise = this.viewletService.openViewlet(defaultViewletId, true); - } - - // Case: we closed the last visible viewlet - // Solv: we hide the sidebar - else if (visibleViewlets.length === 1) { - unpinPromise = this.partService.setSideBarHidden(true); - } - - // Case: we closed the default viewlet - // Solv: we open the next visible viewlet from top - else { - unpinPromise = this.viewletService.openViewlet(visibleViewlets.filter(viewlet => viewlet.id !== viewletId)[0].id, true); - } - - unpinPromise.then(() => { - - // then remove from pinned and update switcher - const index = this.pinnedViewlets.indexOf(viewletId); - this.pinnedViewlets.splice(index, 1); - - this.updateViewletSwitcher(); - }); - } - - public isPinned(viewletId: string): boolean { - return this.pinnedViewlets.indexOf(viewletId) >= 0; - } - - public pin(viewletId: string, update = true): void { - if (this.isPinned(viewletId)) { - return; - } - - // first open that viewlet - this.viewletService.openViewlet(viewletId, true).then(() => { - - // then update - this.pinnedViewlets.push(viewletId); - this.pinnedViewlets = arrays.distinct(this.pinnedViewlets); - - if (update) { - this.updateViewletSwitcher(); - } - }); - } - - public move(viewletId: string, toViewletId: string): void { - - // Make sure a moved viewlet gets pinned - if (!this.isPinned(viewletId)) { - this.pin(viewletId, false /* defer update, we take care of it */); - } - - const fromIndex = this.pinnedViewlets.indexOf(viewletId); - const toIndex = this.pinnedViewlets.indexOf(toViewletId); - - this.pinnedViewlets.splice(fromIndex, 1); - this.pinnedViewlets.splice(toIndex, 0, viewletId); - - // Clear viewlets that are impacted by the move - const visibleViewlets = Object.keys(this.viewletIdToActions); - for (let i = Math.min(fromIndex, toIndex); i < visibleViewlets.length; i++) { - this.pullViewlet(visibleViewlets[i]); - } - - // timeout helps to prevent artifacts from showing up - setTimeout(() => { - this.updateViewletSwitcher(); - }, 0); + return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id));; } /** @@ -542,16 +188,20 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.dimension = sizes[1]; - // Update switcher to handle overflow issues - this.updateViewletSwitcher(); + let availableHeight = this.dimension.height; + if (this.globalActionBar) { + // adjust height for global actions showing + availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTIVITY_ACTION_HEIGHT); + } + this.compositeBar.layout(new Dimension(dimension.width, availableHeight)); return sizes; } public dispose(): void { - if (this.viewletSwitcherBar) { - this.viewletSwitcherBar.dispose(); - this.viewletSwitcherBar = null; + if (this.compositeBar) { + this.compositeBar.dispose(); + this.compositeBar = null; } if (this.globalActionBar) { @@ -565,9 +215,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { public shutdown(): void { // Persist Hidden State - this.memento[ActivitybarPart.PINNED_VIEWLETS] = this.pinnedViewlets; + this.compositeBar.store(); // Pass to super super.shutdown(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts new file mode 100644 index 00000000000..74ea9884843 --- /dev/null +++ b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts @@ -0,0 +1,419 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Action } from 'vs/base/common/actions'; +import { illegalArgument } from 'vs/base/common/errors'; +import * as dom from 'vs/base/browser/dom'; +import * as arrays from 'vs/base/common/arrays'; +import { Dimension } from 'vs/base/browser/builder'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IBadge } from 'vs/workbench/services/activity/common/activityBarService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ActionBar, IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import Event, { Emitter } from 'vs/base/common/event'; +import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; +import { TPromise } from 'vs/base/common/winjs.base'; + +export interface ICompositeBarOptions { + label: 'icon' | 'name'; + storageId: string; + orientation: ActionsOrientation; + composites: { id: string, name: string }[]; + getActivityAction: (compositeId: string) => ActivityAction; + getCompositePinnedAction: (compositeId: string) => Action; + getOnCompositeClickAction: (compositeId: string) => Action; + openComposite: (compositeId: string) => TPromise; + getDefaultCompositeId: () => string; + getCompositeSize: (compositeId: string) => number; + hidePart: () => TPromise; +} + +export class CompositeBar implements ICompositeBar { + + private _onDidContextMenu: Emitter; + + private dimension: Dimension; + private toDispose: IDisposable[]; + + private compositeSwitcherBar: ActionBar; + private compositeOverflowAction: CompositeOverflowActivityAction; + private compositeOverflowActionItem: CompositeOverflowActivityActionItem; + + private compositeIdToActions: { [compositeId: string]: ActivityAction; }; + private compositeIdToActionItems: { [compositeId: string]: IActionItem; }; + private compositeIdToActivityStack: { [compositeId: string]: ICompositeActivity[]; }; + + private pinnedComposites: string[]; + private activeCompositeId: string; + private activeUnpinnedCompositeId: string; + + constructor( + private options: ICompositeBarOptions, + @IInstantiationService private instantiationService: IInstantiationService, + @IStorageService private storageService: IStorageService, + ) { + this.toDispose = []; + this.compositeIdToActionItems = Object.create(null); + this.compositeIdToActions = Object.create(null); + this.compositeIdToActivityStack = Object.create(null); + + this._onDidContextMenu = new Emitter(); + + const pinnedComposites = JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, null)) as string[]; + if (pinnedComposites) { + this.pinnedComposites = pinnedComposites; + } else { + this.pinnedComposites = this.options.composites.map(c => c.id); + } + } + + public get onDidContextMenu(): Event { + return this._onDidContextMenu.event; + } + + public activateComposite(id: string): void { + if (this.compositeIdToActions[id]) { + this.compositeIdToActions[id].activate(); + } + this.activeCompositeId = id; + + const activeUnpinnedCompositeShouldClose = this.activeUnpinnedCompositeId && this.activeUnpinnedCompositeId !== id; + const activeUnpinnedCompositeShouldShow = !this.pinnedComposites.some(pid => pid === id); + if (activeUnpinnedCompositeShouldShow || activeUnpinnedCompositeShouldClose) { + this.updateCompositeSwitcher(); + } + } + + public deactivateComposite(id: string): void { + if (this.compositeIdToActions[id]) { + this.compositeIdToActions[id].deactivate(); + } + } + + public showActivity(compositeId: string, badge: IBadge, clazz?: string): IDisposable { + if (!badge) { + throw illegalArgument('badge'); + } + + const activity = { badge, clazz }; + const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []); + stack.unshift(activity); + + this.updateActivity(compositeId); + + return { + dispose: () => { + const stack = this.compositeIdToActivityStack[compositeId]; + if (!stack) { + return; + } + + const idx = stack.indexOf(activity); + if (idx < 0) { + return; + } + + stack.splice(idx, 1); + if (stack.length === 0) { + delete this.compositeIdToActivityStack[compositeId]; + } + + this.updateActivity(compositeId); + } + }; + } + + private updateActivity(compositeId: string) { + const action = this.compositeIdToActions[compositeId]; + if (!action) { + return; + } + + const stack = this.compositeIdToActivityStack[compositeId]; + + // reset + if (!stack || !stack.length) { + action.setBadge(undefined); + } + + // update + else { + const [{ badge, clazz }] = stack; + action.setBadge(badge); + if (clazz) { + action.class = clazz; + } + } + } + + public create(parent: HTMLElement): void { + const actionBarDiv = parent.appendChild(dom.$('composite-bar')); + this.compositeSwitcherBar = new ActionBar(actionBarDiv, { + actionItemProvider: (action: Action) => action instanceof CompositeOverflowActivityAction ? this.compositeOverflowActionItem : this.compositeIdToActionItems[action.id], + orientation: this.options.orientation, + ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"), + animated: false + }); + this.updateCompositeSwitcher(); + + // Contextmenu for composites + this.toDispose.push(dom.addDisposableListener(parent, dom.EventType.CONTEXT_MENU, (e: MouseEvent) => { + dom.EventHelper.stop(e, true); + this._onDidContextMenu.fire(e); + })); + + // Allow to drop at the end to move composites to the end + this.toDispose.push(dom.addDisposableListener(parent, dom.EventType.DROP, (e: DragEvent) => { + const draggedCompositeId = CompositeActionItem.getDraggedCompositeId(); + if (draggedCompositeId) { + dom.EventHelper.stop(e, true); + CompositeActionItem.clearDraggedComposite(); + + const targetId = this.pinnedComposites[this.pinnedComposites.length - 1]; + if (targetId !== draggedCompositeId) { + this.move(draggedCompositeId, this.pinnedComposites[this.pinnedComposites.length - 1]); + } + } + })); + } + + private updateCompositeSwitcher(): void { + if (!this.compositeSwitcherBar) { + return; // We have not been rendered yet so there is nothing to update. + } + + let compositesToShow = this.pinnedComposites; + + // Always show the active composite even if it is marked to be hidden + if (this.activeCompositeId && !compositesToShow.some(id => id === this.activeCompositeId)) { + this.activeUnpinnedCompositeId = this.activeCompositeId; + compositesToShow = compositesToShow.concat(this.activeUnpinnedCompositeId); + } else { + this.activeUnpinnedCompositeId = void 0; + } + + // Ensure we are not showing more composites than we have height for + let overflows = false; + if (this.dimension) { + let maxVisible = compositesToShow.length; + let size = 0; + const limit = this.options.orientation === ActionsOrientation.VERTICAL ? this.dimension.height : this.dimension.width; + for (let i = 0; i < compositesToShow.length && size <= limit; i++) { + size += this.options.getCompositeSize(compositesToShow[i]); + if (size > limit) { + maxVisible = i; + } + } + overflows = compositesToShow.length > maxVisible; + + if (overflows) { + compositesToShow = compositesToShow.slice(0, maxVisible - 1 /* make room for overflow action */); + } + } + + const visibleComposites = Object.keys(this.compositeIdToActions); + const visibleCompositesChange = !arrays.equals(compositesToShow, visibleComposites); + + // Pull out overflow action if there is a composite change so that we can add it to the end later + if (this.compositeOverflowAction && visibleCompositesChange) { + this.compositeSwitcherBar.pull(this.compositeSwitcherBar.length() - 1); + + this.compositeOverflowAction.dispose(); + this.compositeOverflowAction = null; + + this.compositeOverflowActionItem.dispose(); + this.compositeOverflowActionItem = null; + } + + // Pull out composites that overflow or got hidden + visibleComposites.forEach(compositeId => { + if (compositesToShow.indexOf(compositeId) === -1) { + this.pullComposite(compositeId); + } + }); + + // Built actions for composites to show + const newCompositesToShow = compositesToShow + .filter(compositeId => !this.compositeIdToActions[compositeId]) + .map(compositeId => this.toAction(compositeId)); + + // Update when we have new composites to show + if (newCompositesToShow.length) { + + // Add to composite switcher + this.compositeSwitcherBar.push(newCompositesToShow, { label: true, icon: true }); + + // Make sure to activate the active one + if (this.activeCompositeId) { + const activeCompositeEntry = this.compositeIdToActions[this.activeCompositeId]; + if (activeCompositeEntry) { + activeCompositeEntry.activate(); + } + } + + // Make sure to restore activity + Object.keys(this.compositeIdToActions).forEach(compositeId => { + this.updateActivity(compositeId); + }); + } + + // Add overflow action as needed + if (visibleCompositesChange && overflows) { + this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => this.compositeOverflowActionItem.showMenu()); + this.compositeOverflowActionItem = this.instantiationService.createInstance( + CompositeOverflowActivityActionItem, + this.compositeOverflowAction, + () => this.getOverflowingComposites(), + () => this.activeCompositeId, + (compositeId: string) => this.compositeIdToActivityStack[compositeId] && this.compositeIdToActivityStack[compositeId][0].badge, + this.options.getOnCompositeClickAction + ); + + this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: true, icon: true }); + } + } + + private getOverflowingComposites(): { id: string, name: string }[] { + let overflowingIds = this.pinnedComposites; + if (this.activeUnpinnedCompositeId) { + overflowingIds = overflowingIds.concat(this.activeUnpinnedCompositeId); + } + const visibleComposites = Object.keys(this.compositeIdToActions); + + overflowingIds = overflowingIds.filter(compositeId => visibleComposites.indexOf(compositeId) === -1); + return this.options.composites.filter(c => overflowingIds.indexOf(c.id) !== -1); + } + + private getVisibleComposites(): string[] { + return Object.keys(this.compositeIdToActions); + } + + private pullComposite(compositeId: string): void { + const index = Object.keys(this.compositeIdToActions).indexOf(compositeId); + if (index >= 0) { + this.compositeSwitcherBar.pull(index); + + const action = this.compositeIdToActions[compositeId]; + action.dispose(); + delete this.compositeIdToActions[compositeId]; + + const actionItem = this.compositeIdToActionItems[action.id]; + actionItem.dispose(); + delete this.compositeIdToActionItems[action.id]; + } + } + + private toAction(compositeId: string): ActivityAction { + const compositeActivityAction = this.options.getActivityAction(compositeId); + const pinnedAction = this.options.getCompositePinnedAction(compositeId); + this.compositeIdToActionItems[compositeId] = this.instantiationService.createInstance(CompositeActionItem, compositeActivityAction, pinnedAction, this); + this.compositeIdToActions[compositeId] = compositeActivityAction; + + return compositeActivityAction; + } + + public unpin(compositeId: string): void { + if (!this.isPinned(compositeId)) { + return; + } + + const defaultCompositeId = this.options.getDefaultCompositeId(); + const visibleComposites = this.getVisibleComposites(); + + let unpinPromise: TPromise; + + // Case: composite is not the active one or the active one is a different one + // Solv: we do nothing + if (!this.activeCompositeId || this.activeCompositeId !== compositeId) { + unpinPromise = TPromise.as(null); + } + + // Case: composite is not the default composite and default composite is still showing + // Solv: we open the default composite + else if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) { + unpinPromise = this.options.openComposite(defaultCompositeId); + } + + // Case: we closed the last visible composite + // Solv: we hide the part + else if (visibleComposites.length === 1) { + unpinPromise = this.options.hidePart(); + } + + // Case: we closed the default composite + // Solv: we open the next visible composite from top + else { + unpinPromise = this.options.openComposite(visibleComposites.filter(cid => cid !== compositeId)[0]); + } + + unpinPromise.then(() => { + // then remove from pinned and update switcher + const index = this.pinnedComposites.indexOf(compositeId); + this.pinnedComposites.splice(index, 1); + + this.updateCompositeSwitcher(); + }); + } + + public isPinned(compositeId: string): boolean { + return this.pinnedComposites.indexOf(compositeId) >= 0; + } + + public pin(compositeId: string, update = true): void { + if (this.isPinned(compositeId)) { + return; + } + + this.options.openComposite(compositeId).then(() => { + this.pinnedComposites.push(compositeId); + this.pinnedComposites = arrays.distinct(this.pinnedComposites); + + if (update) { + this.updateCompositeSwitcher(); + } + }); + } + + public move(compositeId: string, toCompositeId: string): void { + // Make sure a moved composite gets pinned + if (!this.isPinned(compositeId)) { + this.pin(compositeId, false /* defer update, we take care of it */); + } + + const fromIndex = this.pinnedComposites.indexOf(compositeId); + const toIndex = this.pinnedComposites.indexOf(toCompositeId); + + this.pinnedComposites.splice(fromIndex, 1); + this.pinnedComposites.splice(toIndex, 0, compositeId); + + // Clear composites that are impacted by the move + const visibleComposites = Object.keys(this.compositeIdToActions); + for (let i = Math.min(fromIndex, toIndex); i < visibleComposites.length; i++) { + this.pullComposite(visibleComposites[i]); + } + + // timeout helps to prevent artifacts from showing up + setTimeout(() => { + this.updateCompositeSwitcher(); + }, 0); + } + + public layout(dimension: Dimension): void { + this.dimension = dimension; + this.updateCompositeSwitcher(); + } + + public store(): void { + this.storageService.store(this.options.storageId, JSON.stringify(this.pinnedComposites), StorageScope.GLOBAL); + } + + public dispose(): void { + this.toDispose = dispose(this.toDispose); + } +} diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts new file mode 100644 index 00000000000..617c17f714d --- /dev/null +++ b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts @@ -0,0 +1,588 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Action } from 'vs/base/common/actions'; +import { TPromise } from 'vs/base/common/winjs.base'; +import * as dom from 'vs/base/browser/dom'; +import { Builder, $ } from 'vs/base/browser/builder'; +import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { dispose } from 'vs/base/common/lifecycle'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activityBarService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme'; +import { DelayedDragHandler } from 'vs/base/browser/dnd'; +import { IActivity } from 'vs/workbench/common/activity'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import Event, { Emitter } from 'vs/base/common/event'; + +export interface ICompositeActivity { + badge: IBadge; + clazz: string; +} + +export interface ICompositeBar { + /** + * Unpins a viewlet from the activitybar. + */ + unpin(viewletId: string): void; + + /** + * Pin a viewlet inside the activity bar. + */ + pin(viewletId: string): void; + + /** + * Find out if a viewlet is pinned in the activity bar. + */ + isPinned(viewletId: string): boolean; + + /** + * Reorder viewlet ordering by moving a viewlet to the location of another viewlet. + */ + move(viewletId: string, toViewletId: string): void; +} + +export class ActivityAction extends Action { + private badge: IBadge; + private _onDidChangeBadge = new Emitter(); + + constructor(private _activity: IActivity) { + super(_activity.id, _activity.name, _activity.cssClass); + + this.badge = null; + } + + public get activity(): IActivity { + return this._activity; + } + + public get onDidChangeBadge(): Event { + return this._onDidChangeBadge.event; + } + + public activate(): void { + if (!this.checked) { + this._setChecked(true); + } + } + + public deactivate(): void { + if (this.checked) { + this._setChecked(false); + } + } + + public getBadge(): IBadge { + return this.badge; + } + + public setBadge(badge: IBadge): void { + this.badge = badge; + this._onDidChangeBadge.fire(this); + } +} + +export class ActivityActionItem extends BaseActionItem { + protected $container: Builder; + protected $label: Builder; + protected $badge: Builder; + + private $badgeContent: Builder; + private mouseUpTimeout: number; + + constructor( + action: ActivityAction, + options: IBaseActionItemOptions, + @IThemeService protected themeService: IThemeService + ) { + super(null, action, options); + + this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose); + action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose); + } + + protected get activity(): IActivity { + return (this._action as ActivityAction).activity; + } + + protected updateStyles(): void { + const theme = this.themeService.getTheme(); + + // Label + if (this.$label) { + const background = theme.getColor(ACTIVITY_BAR_FOREGROUND); + + this.$label.style('background-color', background ? background.toString() : null); + } + + // Badge + if (this.$badgeContent) { + const badgeForeground = theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND); + const badgeBackground = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND); + const contrastBorderColor = theme.getColor(contrastBorder); + + this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null); + this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null); + + this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null); + this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null); + this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null); + } + } + + public render(container: HTMLElement): void { + super.render(container); + + // Make the container tab-able for keyboard navigation + this.$container = $(container).attr({ + tabIndex: '0', + role: 'button', + title: this.activity.name + }); + + // Try hard to prevent keyboard only focus feedback when using mouse + this.$container.on(dom.EventType.MOUSE_DOWN, () => { + this.$container.addClass('clicked'); + }); + + this.$container.on(dom.EventType.MOUSE_UP, () => { + if (this.mouseUpTimeout) { + clearTimeout(this.mouseUpTimeout); + } + + this.mouseUpTimeout = setTimeout(() => { + this.$container.removeClass('clicked'); + }, 800); // delayed to prevent focus feedback from showing on mouse up + }); + + // Label + this.$label = $('a.action-label').appendTo(this.builder); + if (this.activity.cssClass) { + this.$label.addClass(this.activity.cssClass); + } + + this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => { + this.$badgeContent = badge.div({ 'class': 'badge-content' }); + }); + + this.$badge.hide(); + + this.updateStyles(); + } + + private onThemeChange(theme: ITheme): void { + this.updateStyles(); + } + + public setBadge(badge: IBadge): void { + this.updateBadge(badge); + } + + protected updateBadge(badge: IBadge): void { + this.$badgeContent.empty(); + this.$badge.hide(); + + if (badge) { + + // Number + if (badge instanceof NumberBadge) { + if (badge.number) { + this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString()); + this.$badge.show(); + } + } + + // Text + else if (badge instanceof TextBadge) { + this.$badgeContent.text(badge.text); + this.$badge.show(); + } + + // Text + else if (badge instanceof IconBadge) { + this.$badge.show(); + } + + // Progress + else if (badge instanceof ProgressBadge) { + this.$badge.show(); + } + } + + // Title + let title: string; + if (badge && badge.getDescription()) { + if (this.activity.name) { + title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription()); + } else { + title = badge.getDescription(); + } + } else { + title = this.activity.name; + } + + [this.$label, this.$badge, this.$container].forEach(b => { + if (b) { + b.attr('aria-label', title); + b.title(title); + } + }); + } + + private handleBadgeChangeEvenet(): void { + const action = this.getAction(); + if (action instanceof ActivityAction) { + this.updateBadge(action.getBadge()); + } + } + + public dispose(): void { + super.dispose(); + + if (this.mouseUpTimeout) { + clearTimeout(this.mouseUpTimeout); + } + + this.$badge.destroy(); + } +} + +export class CompositeOverflowActivityAction extends ActivityAction { + + constructor( + private showMenu: () => void + ) { + super({ + id: 'activitybar.additionalComposites.action', + name: nls.localize('additionalViews', "Additional Views"), + cssClass: 'toggle-more' + }); + } + + public run(event: any): TPromise { + this.showMenu(); + + return TPromise.as(true); + } +} + +export class CompositeOverflowActivityActionItem extends ActivityActionItem { + private name: string; + private cssClass: string; + private actions: Action[]; + + constructor( + action: ActivityAction, + private getOverflowingComposites: () => { id: string, name: string }[], + private getActiveCompositeId: () => string, + private getBadge: (compositeId: string) => IBadge, + private getCompositeOpenAction: (compositeId: string) => Action, + @IInstantiationService private instantiationService: IInstantiationService, + @IContextMenuService private contextMenuService: IContextMenuService, + @IThemeService themeService: IThemeService + ) { + super(action, null, themeService); + + this.cssClass = action.class; + this.name = action.label; + } + + public showMenu(): void { + if (this.actions) { + dispose(this.actions); + } + + this.actions = this.getActions(); + + this.contextMenuService.showContextMenu({ + getAnchor: () => this.builder.getHTMLElement(), + getActions: () => TPromise.as(this.actions), + onHide: () => dispose(this.actions) + }); + } + + private getActions(): Action[] { + return this.getOverflowingComposites().map(composite => { + const action = this.getCompositeOpenAction(composite.id); + action.radio = this.getActiveCompositeId() === action.id; + + const badge = this.getBadge(composite.id); + let suffix: string | number; + if (badge instanceof NumberBadge) { + suffix = badge.number; + } else if (badge instanceof TextBadge) { + suffix = badge.text; + } + + if (suffix) { + action.label = nls.localize('numberBadge', "{0} ({1})", composite.name, suffix); + } else { + action.label = composite.name; + } + + return action; + }); + } + + public dispose(): void { + super.dispose(); + + this.actions = dispose(this.actions); + } +} + +class ManageExtensionAction extends Action { + + constructor( + @ICommandService private commandService: ICommandService + ) { + super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension")); + } + + public run(id: string): TPromise { + return this.commandService.executeCommand('_extensions.manage', id); + } +} + +export class CompositeActionItem extends ActivityActionItem { + + private static manageExtensionAction: ManageExtensionAction; + private static draggedCompositeId: string; + + private compositeActivity: IActivity; + private cssClass: string; + + constructor( + private compositeActivityAction: ActivityAction, + private toggleCompositePinnedAction: Action, + private compositeBar: ICompositeBar, + @IContextMenuService private contextMenuService: IContextMenuService, + @IKeybindingService private keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService + ) { + super(compositeActivityAction, { draggable: true }, themeService); + + this.cssClass = compositeActivityAction.class; + + if (!CompositeActionItem.manageExtensionAction) { + CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); + } + } + + protected get activity(): IActivity { + if (!this.compositeActivity) { + let activityName: string; + + const keybinding = this.getKeybindingLabel(this.compositeActivityAction.activity.id); + if (keybinding) { + activityName = nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding); + } else { + activityName = this.compositeActivityAction.activity.name; + } + + this.compositeActivity = { + id: this.compositeActivityAction.activity.id, + cssClass: this.cssClass, + name: activityName + }; + } + + return this.compositeActivity; + } + + private getKeybindingLabel(id: string): string { + const kb = this.keybindingService.lookupKeybinding(id); + if (kb) { + return kb.getLabel(); + } + + return null; + } + + public render(container: HTMLElement): void { + super.render(container); + + this.$container.on('contextmenu', e => { + dom.EventHelper.stop(e, true); + + this.showContextMenu(container); + }); + + // Allow to drag + this.$container.on(dom.EventType.DRAG_START, (e: DragEvent) => { + e.dataTransfer.effectAllowed = 'move'; + this.setDraggedComposite(this.activity.id); + + // Trigger the action even on drag start to prevent clicks from failing that started a drag + if (!this.getAction().checked) { + this.getAction().run(); + } + }); + + // Drag enter + let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470 + this.$container.on(dom.EventType.DRAG_ENTER, (e: DragEvent) => { + const draggedCompositeId = CompositeActionItem.getDraggedCompositeId(); + if (draggedCompositeId && draggedCompositeId !== this.activity.id) { + counter++; + this.updateFromDragging(container, true); + } + }); + + // Drag leave + this.$container.on(dom.EventType.DRAG_LEAVE, (e: DragEvent) => { + const draggedCompositeId = CompositeActionItem.getDraggedCompositeId(); + if (draggedCompositeId) { + counter--; + if (counter === 0) { + this.updateFromDragging(container, false); + } + } + }); + + // Drag end + this.$container.on(dom.EventType.DRAG_END, (e: DragEvent) => { + const draggedCompositeId = CompositeActionItem.getDraggedCompositeId(); + if (draggedCompositeId) { + counter = 0; + this.updateFromDragging(container, false); + + CompositeActionItem.clearDraggedComposite(); + } + }); + + // Drop + this.$container.on(dom.EventType.DROP, (e: DragEvent) => { + dom.EventHelper.stop(e, true); + + const draggedCompositeId = CompositeActionItem.getDraggedCompositeId(); + if (draggedCompositeId && draggedCompositeId !== this.activity.id) { + this.updateFromDragging(container, false); + CompositeActionItem.clearDraggedComposite(); + + this.compositeBar.move(draggedCompositeId, this.activity.id); + } + }); + + // Activate on drag over to reveal targets + [this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => { + if (!CompositeActionItem.getDraggedCompositeId() && !this.getAction().checked) { + this.getAction().run(); + } + })); + + this.updateStyles(); + } + + private updateFromDragging(element: HTMLElement, isDragging: boolean): void { + const theme = this.themeService.getTheme(); + const dragBackground = theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND); + + element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null; + } + + public static getDraggedCompositeId(): string { + return CompositeActionItem.draggedCompositeId; + } + + private setDraggedComposite(compositeId: string): void { + CompositeActionItem.draggedCompositeId = compositeId; + } + + public static clearDraggedComposite(): void { + CompositeActionItem.draggedCompositeId = void 0; + } + + private showContextMenu(container: HTMLElement): void { + const actions: Action[] = [this.toggleCompositePinnedAction]; + if ((this.compositeActivityAction.activity).extensionId) { + actions.push(new Separator()); + actions.push(CompositeActionItem.manageExtensionAction); + } + + const isPinned = this.compositeBar.isPinned(this.activity.id); + if (isPinned) { + this.toggleCompositePinnedAction.label = nls.localize('removeFromActivityBar', "Hide from Activity Bar"); + this.toggleCompositePinnedAction.checked = false; + } else { + this.toggleCompositePinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar"); + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => container, + getActionsContext: () => this.activity.id, + getActions: () => TPromise.as(actions) + }); + } + + public focus(): void { + this.$container.domFocus(); + } + + protected _updateClass(): void { + if (this.cssClass) { + this.$badge.removeClass(this.cssClass); + } + + this.cssClass = this.getAction().class; + this.$badge.addClass(this.cssClass); + } + + protected _updateChecked(): void { + if (this.getAction().checked) { + this.$container.addClass('checked'); + } else { + this.$container.removeClass('checked'); + } + } + + protected _updateEnabled(): void { + if (this.getAction().enabled) { + this.builder.removeClass('disabled'); + } else { + this.builder.addClass('disabled'); + } + } + + public dispose(): void { + super.dispose(); + + CompositeActionItem.clearDraggedComposite(); + + this.$label.destroy(); + } +} + +export class ToggleCompositePinnedAction extends Action { + + constructor( + private activity: IActivity, + private compositeBar: ICompositeBar + ) { + super('activitybar.show.toggleViewletPinned', activity ? activity.name : nls.localize('toggle', "Toggle View Pinned")); + + this.checked = this.activity && this.compositeBar.isPinned(this.activity.id); + } + + public run(context: string): TPromise { + const id = this.activity ? this.activity.id : context; + + if (this.compositeBar.isPinned(id)) { + this.compositeBar.unpin(id); + } else { + this.compositeBar.pin(id); + } + + return TPromise.as(true); + } +} diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index e5161192096..376a4c15c0d 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -336,7 +336,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupThre registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category); registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'View: Clear Recently Opened', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File")); registry.registerWorkbenchAction(new SyncActionDescriptor(KeepEditorAction, KeepEditorAction.ID, KeepEditorAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter) }), 'View: Keep Editor', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index f7653de9370..af1a3ec1a9d 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -73,6 +73,7 @@ export interface IEditorGroupsControl { getInstantiationService(position: Position): IInstantiationService; getProgressBar(position: Position): ProgressBar; updateProgress(position: Position, state: ProgressState): void; + updateTitleAreas(refreshActive?: boolean): void; layout(dimension: Dimension): void; layout(position: Position): void; @@ -2080,6 +2081,32 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro return silo ? silo.child().getProperty(key) : void 0; } + public updateTitleAreas(refreshActive?: boolean): void { + POSITIONS.forEach(position => { + const group = this.stacks.groupAt(position); + if (!group) { + return; + } + + const titleControl = this.getTitleAreaControl(position); + if (!titleControl) { + return; + } + + // Make sure the active group is shown in the title + // and refresh it if we are instructed to refresh it + if (refreshActive && group.isActive) { + titleControl.setContext(group); + titleControl.refresh(true); + } + + // Otherwise, just refresh the toolbar + else { + titleControl.updateEditorActionsToolbar(); + } + }); + } + public updateProgress(position: Position, state: ProgressState): void { const progressbar = this.getProgressBar(position); if (!progressbar) { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index d97142a5807..0899f3bd851 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -21,14 +21,14 @@ 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 } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, ConfirmResult, IWorkbenchEditorConfiguration, TextEditorOptions, SideBySideEditorInput, TextCompareEditorVisible, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, ConfirmResult, IWorkbenchEditorConfiguration, TextEditorOptions, SideBySideEditorInput, TextCompareEditorVisible, TEXT_DIFF_EDITOR_ID, EditorOpeningEvent, IEditorOpeningEvent } 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, IEditorTabOptions, IMoveOptions } from 'vs/workbench/services/group/common/groupService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEditorPart } from 'vs/workbench/services/editor/browser/editorService'; +import { IEditorPart } from 'vs/workbench/services/editor/common/editorService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { Position, POSITIONS, Direction } from 'vs/platform/editor/common/editor'; +import { Position, POSITIONS, Direction, IEditor } from 'vs/platform/editor/common/editor'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -99,6 +99,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService private revealIfOpen: boolean; private _onEditorsChanged: Emitter; + private _onEditorOpening: Emitter; private _onEditorsMoved: Emitter; private _onEditorOpenFail: Emitter; private _onGroupOrientationChanged: Emitter; @@ -132,6 +133,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService super(id, { hasTitle: false }, themeService); this._onEditorsChanged = new Emitter(); + this._onEditorOpening = new Emitter(); this._onEditorsMoved = new Emitter(); this._onEditorOpenFail = new Emitter(); this._onGroupOrientationChanged = new Emitter(); @@ -203,7 +205,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService this.toUnbind.push(this.stacks.onEditorClosed(event => this.onEditorClosed(event))); this.toUnbind.push(this.stacks.onGroupOpened(event => this.onEditorGroupOpenedOrClosed())); this.toUnbind.push(this.stacks.onGroupClosed(event => this.onEditorGroupOpenedOrClosed())); - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); } private onEditorGroupOpenedOrClosed(): void { @@ -289,6 +291,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService return this._onEditorsChanged.event; } + public get onEditorOpening(): Event { + return this._onEditorOpening.event; + } + public get onEditorsMoved(): Event { return this._onEditorsMoved.event; } @@ -309,26 +315,40 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService return this.tabOptions; } - public openEditor(input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; - public openEditor(input: EditorInput, options?: EditorOptions, position?: Position, ratio?: number[]): TPromise; - public openEditor(input: EditorInput, options?: EditorOptions, arg3?: any, ratio?: number[]): TPromise { - - // Normalize some values - if (!options) { options = null; } + public openEditor(input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; + public openEditor(input: EditorInput, options?: EditorOptions, position?: Position, ratio?: number[]): TPromise; + public openEditor(input: EditorInput, options?: EditorOptions, arg3?: any, ratio?: number[]): TPromise { + if (!options) { + options = null; + } // Determine position to open editor in (one, two, three) const position = this.findPosition(input, options, arg3, ratio); // Some conditions under which we prevent the request if ( - !input || // no input - position === null || // invalid position - !this.editorGroupsControl || // too early - this.editorGroupsControl.isDragging() // pending editor DND + !input || // no input + position === null || // invalid position + !this.editorGroupsControl || // too early + this.editorGroupsControl.isDragging() // pending editor DND ) { return TPromise.as(null); } + // Editor opening event (can be prevented and overridden) + const event = new EditorOpeningEvent(input, options, position); + this._onEditorOpening.fire(event); + const prevented = event.isPrevented(); + if (prevented) { + return prevented(); + } + + // Open through UI + return this.doOpenEditor(position, input, options, ratio); + } + + private doOpenEditor(position: Position, input: EditorInput, options: EditorOptions, ratio: number[]): TPromise { + // We need an editor descriptor for the input const descriptor = Registry.as(EditorExtensions.Editors).getEditor(input); if (!descriptor) { @@ -345,12 +365,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService this.telemetryService.publicLog('workbenchSideEditorOpened', { position: position }); } - // Open through UI - return this.doOpenEditor(position, descriptor, input, options, ratio); - } - - private doOpenEditor(position: Position, descriptor: IEditorDescriptor, input: EditorInput, options: EditorOptions, ratio: number[]): TPromise { - // Update stacks: We do this early on before the UI is there because we want our stacks model to have // a consistent view of the editor world and updating it later async after the UI is there will cause // issues (e.g. when a closeEditor call is made that expects the openEditor call to have updated the @@ -358,7 +372,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // This can however cause a race condition where the stacks model indicates the opened editor is there // while the UI is not yet ready. Clients have to deal with this fact and we have to make sure that the // stacks model gets updated if any of the UI updating fails with an error. - const group = this.ensureGroup(position, !options || !options.preserveFocus); + const [group, newGroupOpened] = this.ensureGroup(position, !options || !options.preserveFocus); const pinned = !this.tabOptions.previewEditors || (options && (options.pinned || typeof options.index === 'number')) || input.isDirty(); const active = (group.count === 0) || !options || !options.inactive; @@ -387,7 +401,19 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService } // Set input to editor - return this.doSetInput(group, editor, input, options, monitor); + const inputPromise = this.doSetInput(group, editor, input, options, monitor); + + // A new active group got opened. Since this involves updating the title area controls to show + // the new editor and actions we trigger a direct update of title controls from here to avoid + // some UI flickering if we rely on the event handlers that all use schedulers. + // The reason we can trigger this now is that after the input is set to the editor group, the + // resource context is updated and the correct number of actions will be resolved from the title + // area. + if (newGroupOpened && this.stacks.isActive(group)) { + this.editorGroupsControl.updateTitleAreas(true /* refresh new active group */); + } + + return inputPromise; } private doShowEditor(group: EditorGroup, descriptor: IEditorDescriptor, input: EditorInput, options: EditorOptions, ratio: number[], monitor: ProgressMonitor): BaseEditor { @@ -629,6 +655,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Explicitly trigger the focus changed handler because the side by side control will not trigger it unless // the user is actively changing focus with the mouse from left/top to right/bottom. this.onGroupFocusChanged(); + + // Update title area sync to avoid some flickering with actions + this.editorGroupsControl.updateTitleAreas(); } } @@ -1007,7 +1036,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService } } - public replaceEditors(editors: { toReplace: EditorInput, replaceWith: EditorInput, options?: EditorOptions }[], position?: Position): TPromise { + public replaceEditors(editors: { toReplace: EditorInput, replaceWith: EditorInput, options?: EditorOptions }[], position?: Position): TPromise { const activeReplacements: IEditorReplacement[] = []; const hiddenReplacements: IEditorReplacement[] = []; @@ -1066,9 +1095,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService return res; } - public openEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[]): TPromise { + public openEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[]): TPromise { if (!editors.length) { - return TPromise.as([]); + return TPromise.as([]); } let activePosition: Position; @@ -1085,7 +1114,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService return this.stacks.groups.some(g => g.count > 0); } - public restoreEditors(): TPromise { + public restoreEditors(): TPromise { const editors = this.stacks.groups.map((group, index) => { return { input: group.activeEditor, @@ -1095,7 +1124,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService }); if (!editors.length) { - return TPromise.as([]); + return TPromise.as([]); } let activePosition: Position; @@ -1108,7 +1137,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService return this.doOpenEditors(editors, activePosition, editorState && editorState.ratio); } - private doOpenEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[]): TPromise { + private doOpenEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[]): TPromise { const positionOneEditors = editors.filter(e => e.position === Position.ONE); const positionTwoEditors = editors.filter(e => e.position === Position.TWO); const positionThreeEditors = editors.filter(e => e.position === Position.THREE); @@ -1155,7 +1184,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Open each input respecting the options. Since there can only be one active editor in each // position, we have to pick the first input from each position and add the others as inactive - const promises: TPromise[] = []; + const promises: TPromise[] = []; [positionOneEditors.shift(), positionTwoEditors.shift(), positionThreeEditors.shift()].forEach((editor, position) => { if (!editor) { return; // unused position @@ -1164,7 +1193,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService const input = editor.input; // Resolve editor options - const preserveFocus = (activePosition !== position); + const preserveFocus = (activePosition !== position && ratio && ratio.length > 0); // during restore, preserve focus to reduce flicker let options: EditorOptions; if (editor.options) { options = editor.options; @@ -1343,6 +1372,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Emitters this._onEditorsChanged.dispose(); + this._onEditorOpening.dispose(); this._onEditorsMoved.dispose(); this._onEditorOpenFail.dispose(); @@ -1508,9 +1538,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService array[from] = empty; } - private ensureGroup(position: Position, activate = true): EditorGroup { + private ensureGroup(position: Position, activate = true): [EditorGroup, boolean /* new group opened */] { + let newGroupOpened = false; let group = this.stacks.groupAt(position); if (!group) { + newGroupOpened = true; // Race condition: it could be that someone quickly opens editors one after // the other and we are asked to open an editor in position 2 before position @@ -1533,7 +1565,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService this.stacks.setActive(group); } - return group; + return [group, newGroupOpened]; } private modifyGroups(modification: () => void) { diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index f02a2e68452..fe160365da8 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -22,8 +22,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor'; -import { stripWildcards } from 'vs/base/common/strings'; -import { compareItemsByScore, scoreItem, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; export class EditorPickerEntry extends QuickOpenEntryGroup { private stacks: IEditorStacksModel; @@ -107,16 +106,15 @@ export abstract class BaseEditorPicker extends QuickOpenHandler { return TPromise.as(null); } - const stacks = this.editorGroupService.getStacksModel(); - - searchValue = stripWildcards(searchValue.trim()); + // Prepare search for scoring + const query = prepareQuery(searchValue); const entries = editorEntries.filter(e => { - if (!searchValue) { + if (!query.value) { return true; } - const itemScore = scoreItem(e, searchValue, true, QuickOpenItemAccessor, this.scorerCache); + const itemScore = scoreItem(e, query, true, QuickOpenItemAccessor, this.scorerCache); if (!itemScore.score) { return false; } @@ -127,13 +125,14 @@ export abstract class BaseEditorPicker extends QuickOpenHandler { }); // Sorting - if (searchValue) { + const stacks = this.editorGroupService.getStacksModel(); + if (query.value) { entries.sort((e1, e2) => { if (e1.group !== e2.group) { return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group); } - return compareItemsByScore(e1, e2, searchValue, true, QuickOpenItemAccessor, this.scorerCache); + return compareItemsByScore(e1, e2, query, true, QuickOpenItemAccessor, this.scorerCache); }); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 28a91b2db63..ee23ffe9df8 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -23,7 +23,6 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IEditorAction, ICommonCodeEditor, EndOfLineSequence, IModel } from 'vs/editor/common/editorCommon'; import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/common/linesOperations'; @@ -56,6 +55,7 @@ import { widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/c // TODO@Sandeep layer breaker // tslint:disable-next-line:import-patterns import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport { if (input instanceof SideBySideEditorInput) { @@ -793,14 +793,12 @@ export class ChangeModeAction extends Action { @IModeService private modeService: IModeService, @IModelService private modelService: IModelService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService, @IQuickOpenService private quickOpenService: IQuickOpenService, @IPreferencesService private preferencesService: IPreferencesService, @IInstantiationService private instantiationService: IInstantiationService, @ICommandService private commandService: ICommandService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @IConfigurationEditingService private configurationEditService: IConfigurationEditingService + @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { super(actionId, actionLabel); } @@ -966,7 +964,7 @@ export class ChangeModeAction extends Action { TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).done(() => { this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).done(language => { if (language) { - const fileAssociationsConfig = this.configurationService.lookup(ChangeModeAction.FILE_ASSOCIATION_KEY); + const fileAssociationsConfig = this.configurationService.inspect(ChangeModeAction.FILE_ASSOCIATION_KEY); let associationKey: string; if (extension && basename[0] !== '.') { @@ -989,8 +987,7 @@ export class ChangeModeAction extends Action { currentAssociations[associationKey] = language.id; - // Write config - this.configurationEditingService.writeConfiguration(target, { key: ChangeModeAction.FILE_ASSOCIATION_KEY, value: currentAssociations }); + this.configurationService.updateValue(ChangeModeAction.FILE_ASSOCIATION_KEY, currentAssociations, target); } }); }); @@ -1229,7 +1226,7 @@ class ScreenReaderDetectedExplanation { anchorElement: HTMLElement, @IThemeService private readonly themeService: IThemeService, @IContextViewService private readonly contextViewService: IContextViewService, - @IConfigurationEditingService private readonly configurationEditingService: IConfigurationEditingService, + @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, ) { this._isDisposed = false; this._toDispose = []; @@ -1281,20 +1278,14 @@ class ScreenReaderDetectedExplanation { const yesBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerYes', "Yes")); this._toDispose.push(addDisposableListener(yesBtn, 'click', () => { - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { - key: 'editor.accessibilitySupport', - value: 'on' - }); + this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER); this.contextViewService.hideContextView(); })); domNode.appendChild(yesBtn); const noBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerNo', "No")); this._toDispose.push(addDisposableListener(noBtn, 'click', () => { - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { - key: 'editor.accessibilitySupport', - value: 'off' - }); + this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER); this.contextViewService.hideContextView(); })); domNode.appendChild(noBtn); diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index fe92a09cb72..e598d3383e3 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -20,7 +20,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IMessageService } from 'vs/platform/message/common/message'; @@ -37,7 +37,6 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { extractResources } from 'vs/base/browser/dnd'; import { getOrSet } from 'vs/base/common/map'; -import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER } from 'vs/workbench/common/theme'; @@ -364,7 +363,7 @@ export class TabsTitleControl extends TitleControl { // Gather duplicate titles, while filtering out invalid descriptions const mapTitleToDuplicates = new Map(); for (const label of labels) { - if (typeof label.description === 'string' && label.description) { + if (typeof label.description === 'string') { getOrSet(mapTitleToDuplicates, label.name, []).push(label); } else { label.description = ''; diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 7143b5a9fa2..641e62414b7 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -22,14 +22,13 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; -import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IModeService } from 'vs/editor/common/services/modeService'; diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index b70eced071c..13522be113f 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -66,7 +66,7 @@ export abstract class BaseTextEditor extends BaseEditor { ) { super(id, telemetryService, themeService); - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration(this.getResource())))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration(this.getResource())))); } protected get instantiationService(): IInstantiationService { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 8f78fd0e943..422ab06260c 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -60,6 +60,7 @@ export interface ITitleAreaControl { getContainer(): HTMLElement; refresh(instant?: boolean): void; update(instant?: boolean): void; + updateEditorActionsToolbar(): void; layout(): void; dispose(): void; } @@ -340,7 +341,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl return { primary, secondary }; } - protected updateEditorActionsToolbar(): void { + public updateEditorActionsToolbar(): void { const group = this.context; if (!group) { return; diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 27b098e9604..3a666cfbc1c 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -55,7 +55,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { FileKind, IFileService } from 'vs/platform/files/common/files'; -import { scoreItem, ScorerCache, compareItemsByScore } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; const HELP_PREFIX = '?'; @@ -137,7 +137,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } private registerListeners(): void { - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.updateConfiguration(this.configurationService.getConfiguration()))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration(this.configurationService.getConfiguration()))); this.toUnbind.push(this.partService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget())); this.toUnbind.push(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget())); } @@ -545,6 +545,7 @@ export class QuickOpenController extends Component implements IQuickOpenService public show(prefix?: string, options?: IShowOptions): TPromise { let quickNavigateConfiguration = options ? options.quickNavigateConfiguration : void 0; let inputSelection = options ? options.inputSelection : void 0; + let autoFocus = options ? options.autoFocus : void 0; this.previousValue = prefix; @@ -565,8 +566,7 @@ export class QuickOpenController extends Component implements IQuickOpenService this.telemetryService.publicLog('quickOpenWidgetShown', { mode: handlerDescriptor.getId(), quickNavigate: quickNavigateConfiguration }); // Trigger onOpen - this.resolveHandler(handlerDescriptor) - .done(null, errors.onUnexpectedError); + this.resolveHandler(handlerDescriptor).done(null, errors.onUnexpectedError); // Create upon first open if (!this.quickOpenWidget) { @@ -601,19 +601,21 @@ export class QuickOpenController extends Component implements IQuickOpenService // Show quick open with prefix or editor history if (!this.quickOpenWidget.isVisible() || quickNavigateConfiguration) { if (prefix) { - this.quickOpenWidget.show(prefix, { quickNavigateConfiguration, inputSelection }); + this.quickOpenWidget.show(prefix, { quickNavigateConfiguration, inputSelection, autoFocus }); } else { const editorHistory = this.getEditorHistoryWithGroupLabel(); if (editorHistory.getEntries().length < 2) { quickNavigateConfiguration = null; // If no entries can be shown, default to normal quick open mode } - let autoFocus: IAutoFocus; - if (!quickNavigateConfiguration) { - autoFocus = { autoFocusFirstEntry: true }; - } else { - const visibleEditorCount = this.editorService.getVisibleEditors().length; - autoFocus = { autoFocusFirstEntry: visibleEditorCount === 0, autoFocusSecondEntry: visibleEditorCount !== 0 }; + // Compute auto focus + if (!autoFocus) { + if (!quickNavigateConfiguration) { + autoFocus = { autoFocusFirstEntry: true }; + } else { + const visibleEditorCount = this.editorService.getVisibleEditors().length; + autoFocus = { autoFocusFirstEntry: visibleEditorCount === 0, autoFocusSecondEntry: visibleEditorCount !== 0 }; + } } // Update context @@ -1177,19 +1179,19 @@ class EditorHistoryHandler { } public getResults(searchValue?: string): QuickOpenEntry[] { - if (searchValue) { - searchValue = strings.stripWildcards(searchValue.replace(/ /g, '')); // get rid of all whitespace and wildcards - } + + // Massage search for scoring + const query = prepareQuery(searchValue); // Just return all if we are not searching const history = this.historyService.getHistory(); - if (!searchValue) { + if (!query.value) { return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)); } // Otherwise filter by search value and sort by score. Include matches on description // in case the user is explicitly including path separators. - const accessor = searchValue.indexOf(paths.nativeSep) >= 0 ? MatchOnDescription : DoNotMatchOnDescription; + const accessor = query.containsPathSeparator ? MatchOnDescription : DoNotMatchOnDescription; return history // For now, only support to match on inputs that provide resource information @@ -1209,7 +1211,7 @@ class EditorHistoryHandler { // Make sure the search value is matching .filter(e => { - const itemScore = scoreItem(e, searchValue, false, accessor, this.scorerCache); + const itemScore = scoreItem(e, query, false, accessor, this.scorerCache); if (!itemScore.score) { return false; } @@ -1221,7 +1223,7 @@ class EditorHistoryHandler { // Sort by score and provide a fallback sorter that keeps the // recency of items in case the score for items is the same - .sort((e1, e2) => compareItemsByScore(e1, e2, searchValue, false, accessor, this.scorerCache, (e1, e2, searchValue, accessor) => -1)); + .sort((e1, e2) => compareItemsByScore(e1, e2, query, false, accessor, this.scorerCache, (e1, e2, query, accessor) => -1)); } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 806c5d7d2c8..62741f2adfc 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -99,7 +99,7 @@ export class TitlebarPart extends Part implements ITitleService { private registerListeners(): void { this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onBlur())); this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.FOCUS, () => this.onFocus())); - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged(true))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationChanged(true))); this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); this.toUnbind.push(this.contextService.onDidChangeWorkspaceFolders(() => this.setTitle(this.getWindowTitle()))); this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(() => this.setTitle(this.getWindowTitle()))); @@ -118,7 +118,7 @@ export class TitlebarPart extends Part implements ITitleService { private onConfigurationChanged(update?: boolean): void { const currentTitleTemplate = this.titleTemplate; - this.titleTemplate = this.configurationService.lookup('window.title').value; + this.titleTemplate = this.configurationService.getValue('window.title'); if (update && currentTitleTemplate !== this.titleTemplate) { this.setTitle(this.getWindowTitle()); diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index d1b8e840b46..63643d4d95a 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -61,8 +61,8 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR }); // menu item - // TODO@Ben slightly weird if-check required because of - // https://github.com/Microsoft/vscode/blob/d28ace31aa147596e35adf101a27768a048c79ec/src/vs/workbench/parts/files/browser/fileActions.contribution.ts#L194 + // TODO@Rob slightly weird if-check required because of + // https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/parts/search/browser/search.contribution.ts#L266 if (descriptor.label) { const command = { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 440ef0a6265..2df8fc14a2d 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -11,7 +11,7 @@ import types = require('vs/base/common/types'); import URI from 'vs/base/common/uri'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IEditor, IEditorViewState, IModel, ScrollType } from 'vs/editor/common/editorCommon'; -import { IEditorInput, IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, Position, Verbosity } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, Position, Verbosity, IEditor as IBaseEditor } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -260,6 +260,48 @@ export abstract class EditorInput implements IEditorInput { } } +export interface IEditorOpeningEvent { + input: IEditorInput; + options?: IEditorOptions; + position: Position; + + /** + * Allows to prevent the opening of an editor by providing a callback + * that will be executed instead. By returning another editor promise + * it is possible to override the opening with another editor. It is ok + * to return a promise that resolves to NULL to prevent the opening + * altogether. + */ + prevent(callback: () => TPromise): void; +} + +export class EditorOpeningEvent { + private override: () => TPromise; + + constructor(private _input: IEditorInput, private _options: IEditorOptions, private _position: Position) { + } + + public get input(): IEditorInput { + return this._input; + } + + public get options(): IEditorOptions { + return this._options; + } + + public get position(): Position { + return this._position; + } + + public prevent(callback: () => TPromise): void { + this.override = callback; + } + + public isPrevented(): () => TPromise { + return this.override; + } +} + export enum EncodingMode { /** diff --git a/src/vs/workbench/common/editor/editorStacksModel.ts b/src/vs/workbench/common/editor/editorStacksModel.ts index 26d0dca609d..148a01d2c41 100644 --- a/src/vs/workbench/common/editor/editorStacksModel.ts +++ b/src/vs/workbench/common/editor/editorStacksModel.ts @@ -110,7 +110,7 @@ export class EditorGroup implements IEditorGroup { } private registerListeners(): void { - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); } private onConfigurationUpdated(config: IWorkbenchEditorConfiguration): void { diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index ea19725e484..4f9a57314ec 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -11,6 +11,7 @@ import { IReference } from 'vs/base/common/lifecycle'; import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; /** * A read-only text editor input whos contents are made of the provided resource that points to an existing @@ -29,7 +30,8 @@ export class ResourceEditorInput extends EditorInput { name: string, description: string, resource: URI, - @ITextModelService private textModelResolverService: ITextModelService + @ITextModelService private textModelResolverService: ITextModelService, + @IHashService private hashService: IHashService ) { super(); @@ -70,7 +72,7 @@ export class ResourceEditorInput extends EditorInput { public getTelemetryDescriptor(): object { const descriptor = super.getTelemetryDescriptor(); - descriptor['resource'] = telemetryURIDescriptor(this.resource); + descriptor['resource'] = telemetryURIDescriptor(this.resource, path => this.hashService.createSHA1(path)); /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 8057bef0160..45092f33e98 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -22,6 +22,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Verbosity } from 'vs/platform/editor/common/editor'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; /** * An editor input to be used for untitled text buffers. @@ -48,7 +49,8 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport @IInstantiationService private instantiationService: IInstantiationService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @ITextFileService private textFileService: ITextFileService, - @IEnvironmentService private environmentService: IEnvironmentService + @IEnvironmentService private environmentService: IEnvironmentService, + @IHashService private hashService: IHashService ) { super(); @@ -252,7 +254,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport public getTelemetryDescriptor(): object { const descriptor = super.getTelemetryDescriptor(); - descriptor['resource'] = telemetryURIDescriptor(this.getResource()); + descriptor['resource'] = telemetryURIDescriptor(this.getResource(), path => this.hashService.createSHA1(path)); /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index 5442ba80ab6..278e1bdcad8 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -94,7 +94,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin private registerListeners(): void { // Config Changes - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange())); } private onConfigurationChange(): void { diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 34b7d02d846..9ffaef6c14f 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -23,11 +23,13 @@ export class ResourceContextKey implements IContextKey { static Filename = new RawContextKey('resourceFilename', undefined); static LangId = new RawContextKey('resourceLangId', undefined); static Resource = new RawContextKey('resource', undefined); + static Extension = new RawContextKey('resourceExtname', undefined); private _resourceKey: IContextKey; private _schemeKey: IContextKey; private _filenameKey: IContextKey; private _langIdKey: IContextKey; + private _extensionKey: IContextKey; constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -37,6 +39,7 @@ export class ResourceContextKey implements IContextKey { this._filenameKey = ResourceContextKey.Filename.bindTo(contextKeyService); this._langIdKey = ResourceContextKey.LangId.bindTo(contextKeyService); this._resourceKey = ResourceContextKey.Resource.bindTo(contextKeyService); + this._extensionKey = ResourceContextKey.Extension.bindTo(contextKeyService); } set(value: URI) { @@ -44,12 +47,14 @@ export class ResourceContextKey implements IContextKey { this._schemeKey.set(value && value.scheme); this._filenameKey.set(value && basename(value.fsPath)); this._langIdKey.set(value && this._modeService.getModeIdByFilenameOrFirstLine(value.fsPath)); + this._extensionKey.set(value && paths.extname(value.fsPath)); } reset(): void { this._schemeKey.reset(); this._langIdKey.reset(); this._resourceKey.reset(); + this._extensionKey.reset(); } public get(): URI { @@ -90,7 +95,7 @@ export class ResourceGlobMatcher { } private registerListeners(): void { - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.updateExcludes(true))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(() => this.updateExcludes(true))); this.toUnbind.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateExcludes(true))); } diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 67796b9bc2f..95281cf2338 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -20,8 +20,7 @@ import errors = require('vs/base/common/errors'); import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IExtensionManagementService, LocalExtensionType, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import paths = require('vs/base/common/paths'); @@ -157,14 +156,13 @@ export class ToggleMenuBarAction extends Action { id: string, label: string, @IMessageService private messageService: IMessageService, - @IConfigurationService private configurationService: IConfigurationService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IConfigurationService private configurationService: IConfigurationService ) { super(id, label); } public run(): TPromise { - let currentVisibilityValue = this.configurationService.lookup(ToggleMenuBarAction.menuBarVisibilityKey).value; + let currentVisibilityValue = this.configurationService.getValue(ToggleMenuBarAction.menuBarVisibilityKey); if (typeof currentVisibilityValue !== 'string') { currentVisibilityValue = 'default'; } @@ -176,7 +174,7 @@ export class ToggleMenuBarAction extends Action { newVisibilityValue = 'default'; } - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleMenuBarAction.menuBarVisibilityKey, value: newVisibilityValue }); + this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER); return TPromise.as(null); } @@ -202,18 +200,12 @@ export abstract class BaseZoomAction extends Action { constructor( id: string, label: string, - @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService ) { super(id, label); } protected setConfiguredZoomLevel(level: number): void { - let target = ConfigurationTarget.USER; - if (typeof this.configurationService.lookup(BaseZoomAction.SETTING_KEY).workspace === 'number') { - target = ConfigurationTarget.WORKSPACE; - } - level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels const applyZoom = () => { @@ -225,7 +217,7 @@ export abstract class BaseZoomAction extends Action { browser.setZoomLevel(webFrame.getZoomLevel(), /*isTrusted*/false); }; - this.configurationEditingService.writeConfiguration(target, { key: BaseZoomAction.SETTING_KEY, value: level }, { donotNotifyError: true }).done(() => applyZoom(), error => applyZoom()); + this.configurationService.updateValue(BaseZoomAction.SETTING_KEY, level).done(() => applyZoom()); } } @@ -237,10 +229,9 @@ export class ZoomInAction extends BaseZoomAction { constructor( id: string, label: string, - @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService + @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService ) { - super(id, label, configurationService, configurationEditingService); + super(id, label, configurationService); } public run(): TPromise { @@ -258,10 +249,9 @@ export class ZoomOutAction extends BaseZoomAction { constructor( id: string, label: string, - @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService + @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService ) { - super(id, label, configurationService, configurationEditingService); + super(id, label, configurationService); } public run(): TPromise { @@ -279,10 +269,9 @@ export class ZoomResetAction extends BaseZoomAction { constructor( id: string, label: string, - @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService + @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService ) { - super(id, label, configurationService, configurationEditingService); + super(id, label, configurationService); } public run(): TPromise { diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index dc469437580..f56ae229cbe 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -84,16 +84,13 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(Naviga workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(IncreaseViewSizeAction, IncreaseViewSizeAction.ID, IncreaseViewSizeAction.LABEL, null), 'View: Increase Current View Size', viewCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DecreaseViewSizeAction, DecreaseViewSizeAction.ID, DecreaseViewSizeAction.LABEL, null), 'View: Decrease Current View Size', viewCategory); -// TODO@Ben multi root -if (product.quality !== 'stable') { - const workspacesCategory = nls.localize('workspaces', "Workspaces"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL), 'Workspaces: Open Workspace Configuration File', workspacesCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAsWorkspaceInNewWindowAction, OpenFolderAsWorkspaceInNewWindowAction.ID, OpenFolderAsWorkspaceInNewWindowAction.LABEL), 'Workspaces: Open Folder as Workspace in New Window', workspacesCategory); -} +const workspacesCategory = nls.localize('workspaces', "Workspaces"); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL), 'Workspaces: Open Workspace Configuration File', workspacesCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAsWorkspaceInNewWindowAction, OpenFolderAsWorkspaceInNewWindowAction.ID, OpenFolderAsWorkspaceInNewWindowAction.LABEL), 'Workspaces: Open Folder as Workspace in New Window', workspacesCategory); // Developer related actions const developerCategory = nls.localize('developer', "Developer"); @@ -322,9 +319,9 @@ Note that there can still be cases where this setting is ignored (e.g. when usin 'default': isMacintosh ? '${activeEditorShort}${separator}${rootName}' : '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}', 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'title' }, `Controls the window title based on the active editor. Variables are substituted based on the context: -\${activeEditorShort}: e.g. the file name (myFile.txt) -\${activeEditorMedium}: e.g. the path of the file relative to the workspace folder (myFolder/myFile.txt) -\${activeEditorLong}: e.g. the full path of the file (/Users/Development/myProject/myFolder/myFile.txt) +\${activeEditorShort}: the file name (e.g. myFile.txt) +\${activeEditorMedium}: the path of the file relative to the workspace folder (e.g. myFolder/myFile.txt) +\${activeEditorLong}: the full path of the file (e.g. /Users/Development/myProject/myFolder/myFile.txt) \${folderName}: name of the workspace folder the file is contained in (e.g. myFolder) \${folderPath}: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder) \${rootName}: name of the workspace (e.g. myFolder or myWorkspace) diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index f461e27e464..7784777acea 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -17,7 +17,7 @@ import paths = require('vs/base/common/paths'); import uri from 'vs/base/common/uri'; import strings = require('vs/base/common/strings'); import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { realpath } from 'vs/base/node/pfs'; diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index c5eb4b88f20..b0a9dff094c 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -31,7 +31,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { ElectronWindow } from 'vs/workbench/electron-browser/window'; import { resolveWorkbenchCommonProperties, getOrCreateMachineId } from 'vs/platform/telemetry/node/workbenchCommonProperties'; import { machineIdIpcChannel } from 'vs/platform/telemetry/node/commonProperties'; -import { WorkspaceStats } from 'vs/workbench/services/telemetry/common/workspaceStats'; +import { WorkspaceStats } from 'vs/workbench/services/telemetry/node/workspaceStats'; import { IWindowsService, IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { WindowService } from 'vs/platform/windows/electron-browser/windowService'; import { MessageService } from 'vs/workbench/services/message/electron-browser/messageService'; @@ -91,6 +91,8 @@ import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollba import { TextMateService } from 'vs/workbench/services/textMate/electron-browser/TMSyntax'; import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; import { IBroadcastService, BroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService'; +import { HashService } from 'vs/workbench/services/hash/node/hashService'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; /** * Services that we require for the Shell @@ -225,9 +227,9 @@ export class WorkbenchShell { userAgent: navigator.userAgent, windowSize: { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }, emptyWorkbench: this.contextService.getWorkbenchState() === WorkbenchState.EMPTY, - 'workbench.filesToOpen': filesToOpen && filesToOpen.length || void 0, - 'workbench.filesToCreate': filesToCreate && filesToCreate.length || void 0, - 'workbench.filesToDiff': filesToDiff && filesToDiff.length || void 0, + 'workbench.filesToOpen': filesToOpen && filesToOpen.length || 0, + 'workbench.filesToCreate': filesToCreate && filesToCreate.length || 0, + 'workbench.filesToDiff': filesToDiff && filesToDiff.length || 0, customKeybindingsCount: info.customKeybindingsCount, theme: this.themeService.getColorTheme().id, language: platform.language, @@ -293,6 +295,9 @@ export class WorkbenchShell { restoreFontInfo(this.storageService); readFontInfo(BareFontInfo.createFromRawSettings(this.configurationService.getConfiguration('editor'), browser.getZoomLevel())); + // Hash + serviceCollection.set(IHashService, new SyncDescriptor(HashService)); + // Experiments this.experimentService = instantiationService.createInstance(ExperimentService); serviceCollection.set(IExperimentService, this.experimentService); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index bdf4e7eeef2..5049e7db79e 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -29,7 +29,6 @@ import { IWindowsService, IWindowService, IWindowSettings, IPath, IOpenFileReque import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME, VS_DARK_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as browser from 'vs/base/browser/browser'; @@ -47,6 +46,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), null, true, () => document.execCommand('undo') && TPromise.as(true)), @@ -81,7 +81,6 @@ export class ElectronWindow extends Themable { @ITitleService private titleService: ITitleService, @IWorkbenchThemeService protected themeService: IWorkbenchThemeService, @IMessageService private messageService: IMessageService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, @ICommandService private commandService: ICommandService, @IExtensionService private extensionService: IExtensionService, @IViewletService private viewletService: IViewletService, @@ -241,7 +240,7 @@ export class ElectronWindow extends Themable { }); // Configuration changes - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onDidUpdateConfiguration(e))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onDidUpdateConfiguration(e))); // Context menu support in input/textarea window.document.addEventListener('contextmenu', e => this.onContextMenu(e)); @@ -498,7 +497,7 @@ export class ElectronWindow extends Themable { } private toggleAutoSave(): void { - const setting = this.configurationService.lookup(ElectronWindow.AUTO_SAVE_SETTING); + const setting = this.configurationService.inspect(ElectronWindow.AUTO_SAVE_SETTING); let userAutoSaveConfig = setting.user; if (types.isUndefinedOrNull(userAutoSaveConfig)) { userAutoSaveConfig = setting.default; // use default if setting not defined @@ -511,7 +510,7 @@ export class ElectronWindow extends Themable { newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY; } - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ElectronWindow.AUTO_SAVE_SETTING, value: newAutoSaveValue }); + this.configurationService.updateValue(ElectronWindow.AUTO_SAVE_SETTING, newAutoSaveValue, ConfigurationTarget.USER); } public dispose(): void { diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index c565be4c877..89a41c79fb2 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -39,16 +39,13 @@ import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbe import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController'; import { getServices } from 'vs/platform/instantiation/common/extensions'; -import { WorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { Position, Parts, IPartService, ILayoutOptions } from 'vs/workbench/services/part/common/partService'; import { IWorkspaceContextService, WorkbenchState } 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'; import { WorkbenchKeybindingService } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configuration'; -import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; +import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationService'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; @@ -66,7 +63,7 @@ import { ConfigurationResolverService } from 'vs/workbench/services/configuratio import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { WorkbenchMessageService } from 'vs/workbench/services/message/browser/messageService'; -import { IWorkbenchEditorService, IResourceInputType } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEditorService, IResourceInputType, WorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; @@ -97,6 +94,8 @@ import { OpenRecentAction, ToggleDevToolsAction, ReloadWindowAction, ShowPreviou import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { WorkspaceEditingService } from 'vs/workbench/services/workspace/node/workspaceEditingService'; +import { FileDecorationsService } from 'vs/workbench/services/decorations/browser/decorationsService'; +import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; import URI from 'vs/base/common/uri'; export const MessagesVisibleContext = new RawContextKey('globalMessageVisible', false); @@ -176,7 +175,6 @@ export class Workbench implements IPartService { private contextKeyService: IContextKeyService; private keybindingService: IKeybindingService; private backupFileService: IBackupFileService; - private configurationEditingService: IConfigurationEditingService; private fileService: IFileService; private titlebarPart: TitlebarPart; private activitybarPart: ActivitybarPart; @@ -407,7 +405,7 @@ export class Workbench implements IPartService { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: isDeveloping ? null : KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', localize('file', "File")); // Actions for macOS native tabs management (only when enabled) - const windowConfig = this.configurationService.getConfiguration(); + const windowConfig = this.configurationService.getConfiguration(); if (windowConfig && windowConfig.window && windowConfig.window.nativeTabs) { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousWindowTab, ShowPreviousWindowTab.ID, ShowPreviousWindowTab.LABEL), 'Show Previous Window Tab'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextWindowTab, ShowNextWindowTab.ID, ShowNextWindowTab.LABEL), 'Show Next Window Tab'); @@ -484,13 +482,13 @@ export class Workbench implements IPartService { } private openUntitledFile() { - const startupEditor = this.configurationService.lookup('workbench.startupEditor'); + const startupEditor = this.configurationService.inspect('workbench.startupEditor'); // Fallback to previous workbench.welcome.enabled setting in case startupEditor is not defined if (!startupEditor.user && !startupEditor.workspace) { - const welcomeEnabled = this.configurationService.lookup('workbench.welcome.enabled'); - if (typeof welcomeEnabled.value === 'boolean') { - return !welcomeEnabled.value; + const welcomeEnabledValue = this.configurationService.getValue('workbench.welcome.enabled'); + if (typeof welcomeEnabledValue === 'boolean') { + return !welcomeEnabledValue; } } @@ -583,6 +581,9 @@ export class Workbench implements IPartService { // Text File Service serviceCollection.set(ITextFileService, new SyncDescriptor(TextFileService)); + // File Decorations + serviceCollection.set(IDecorationsService, new SyncDescriptor(FileDecorationsService)); + // SCM Service serviceCollection.set(ISCMService, new SyncDescriptor(SCMService)); @@ -593,10 +594,6 @@ export class Workbench implements IPartService { const jsonEditingService = this.instantiationService.createInstance(JSONEditingService); serviceCollection.set(IJSONEditingService, jsonEditingService); - // Configuration Editing - this.configurationEditingService = this.instantiationService.createInstance(ConfigurationEditingService); - serviceCollection.set(IConfigurationEditingService, this.configurationEditingService); - // Workspace Editing serviceCollection.set(IWorkspaceEditingService, new SyncDescriptor(WorkspaceEditingService)); @@ -624,6 +621,8 @@ export class Workbench implements IPartService { Registry.as(EditorExtensions.EditorInputFactories).setInstantiationService(this.instantiationService); this.instantiationService.createInstance(DefaultConfigurationExportHelper); + + this.configurationService.setInstantiationService(this.getInstantiationService()); } private initSettings(): void { @@ -639,19 +638,19 @@ export class Workbench implements IPartService { } // Sidebar position - const sideBarPosition = this.configurationService.lookup(Workbench.sidebarPositionConfigurationKey).value; + const sideBarPosition = this.configurationService.getValue(Workbench.sidebarPositionConfigurationKey); this.sideBarPosition = (sideBarPosition === 'right') ? Position.RIGHT : Position.LEFT; // Statusbar visibility - const statusBarVisible = this.configurationService.lookup(Workbench.statusbarVisibleConfigurationKey).value; + const statusBarVisible = this.configurationService.getValue(Workbench.statusbarVisibleConfigurationKey); this.statusBarHidden = !statusBarVisible; // Activity bar visibility - const activityBarVisible = this.configurationService.lookup(Workbench.activityBarVisibleConfigurationKey).value; + const activityBarVisible = this.configurationService.getValue(Workbench.activityBarVisibleConfigurationKey); this.activityBarHidden = !activityBarVisible; // Font aliasing - this.fontAliasing = this.configurationService.lookup(Workbench.fontAliasingConfigurationKey).value; + this.fontAliasing = this.configurationService.getValue(Workbench.fontAliasingConfigurationKey); // Zen mode this.zenMode = { @@ -987,7 +986,7 @@ export class Workbench implements IPartService { this.toDispose.push(this.quickOpen.onHide(() => (this.messageService).resume())); // resume messages once quick open is closed again // Configuration changes - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(() => this.onDidUpdateConfiguration())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(() => this.onDidUpdateConfiguration())); // Fullscreen changes this.toDispose.push(browser.onDidChangeFullscreen(() => this.onFullscreenChanged())); @@ -1036,7 +1035,7 @@ export class Workbench implements IPartService { // Overruled by: window has a workspace opened or this window is for extension development // or setting is disabled. Also enabled when running with --wait from the command line. if (visibleEditors === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { - const closeWhenEmpty = this.configurationService.lookup(Workbench.closeWhenEmptyConfigurationKey).value; + const closeWhenEmpty = this.configurationService.getValue(Workbench.closeWhenEmptyConfigurationKey); if (closeWhenEmpty || this.environmentService.args.wait) { this.closeEmptyWindowScheduler.schedule(); } @@ -1068,24 +1067,24 @@ export class Workbench implements IPartService { } private onDidUpdateConfiguration(skipLayout?: boolean): void { - const newSidebarPositionValue = this.configurationService.lookup(Workbench.sidebarPositionConfigurationKey).value; + const newSidebarPositionValue = this.configurationService.getValue(Workbench.sidebarPositionConfigurationKey); const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT; if (newSidebarPosition !== this.getSideBarPosition()) { this.setSideBarPosition(newSidebarPosition); } - const fontAliasing = this.configurationService.lookup(Workbench.fontAliasingConfigurationKey).value; + const fontAliasing = this.configurationService.getValue(Workbench.fontAliasingConfigurationKey); if (fontAliasing !== this.fontAliasing) { this.setFontAliasing(fontAliasing); } if (!this.zenMode.active) { - const newStatusbarHiddenValue = !this.configurationService.lookup(Workbench.statusbarVisibleConfigurationKey).value; + const newStatusbarHiddenValue = !this.configurationService.getValue(Workbench.statusbarVisibleConfigurationKey); if (newStatusbarHiddenValue !== this.statusBarHidden) { this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); } - const newActivityBarHiddenValue = !this.configurationService.lookup(Workbench.activityBarVisibleConfigurationKey).value; + const newActivityBarHiddenValue = !this.configurationService.getValue(Workbench.activityBarVisibleConfigurationKey); if (newActivityBarHiddenValue !== this.activityBarHidden) { this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); } diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index cda0a718027..a28fb2b6a9b 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -232,13 +232,13 @@ export class ExtensionHostMain { const query: ISearchQuery = { folderQueries, type: QueryType.File, - maxResults: 1, + exists: true, includePattern: includes, useRipgrep }; let result = await this._diskSearch.search(query); - if (result.results.length > 0) { + if (result.limitHit) { // a file was found matching one of the glob patterns return ( this._extensionService.activateById(extensionId, true) diff --git a/src/vs/workbench/parts/backup/common/backupModelTracker.ts b/src/vs/workbench/parts/backup/common/backupModelTracker.ts index 787897cd389..4b1408847d2 100644 --- a/src/vs/workbench/parts/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/parts/backup/common/backupModelTracker.ts @@ -50,7 +50,7 @@ export class BackupModelTracker implements IWorkbenchContribution { this.toDispose.push(this.untitledEditorService.onDidDisposeModel((e) => this.discardBackup(e))); // Listen to config changes - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration()))); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration()))); } private onConfigurationChange(configuration: IFilesConfiguration): void { diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/accessibility.ts b/src/vs/workbench/parts/codeEditor/electron-browser/accessibility.ts index ddbc5ae45e6..aaf8113fe70 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/accessibility.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/accessibility.ts @@ -25,10 +25,9 @@ import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/common/toggleTabFocusMode'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorWidgetBackground, widgetShadow, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import * as editorOptions from 'vs/editor/common/config/editorOptions'; import * as platform from 'vs/base/common/platform'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import URI from 'vs/base/common/uri'; @@ -87,7 +86,6 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { @IContextKeyService private _contextKeyService: IContextKeyService, @IKeybindingService private _keybindingService: IKeybindingService, @IConfigurationService private _configurationService: IConfigurationService, - @IConfigurationEditingService private _configurationEditingService: IConfigurationEditingService, @IOpenerService private _openerService: IOpenerService ) { super(); @@ -124,10 +122,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { if (e.equals(KeyMod.CtrlCmd | KeyCode.KEY_E)) { alert(nls.localize('emergencyConfOn', "Now changing the setting `editor.accessibilitySupport` to 'on'.")); - this._configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { - key: 'editor.accessibilitySupport', - value: 'on' - }); + this._configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER); e.preventDefault(); e.stopPropagation(); diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts index 86cdf51939a..247b6b2e73b 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @editorAction export class ToggleMinimapAction extends EditorAction { @@ -22,10 +22,10 @@ export class ToggleMinimapAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void { - const configurationEditingService = accessor.get(IConfigurationEditingService); + const configurationService = accessor.get(IConfigurationService); const newValue = !editor.getConfiguration().viewInfo.minimap.enabled; - configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: 'editor.minimap.enabled', value: newValue }); + configurationService.updateValue('editor.minimap.enabled', newValue, ConfigurationTarget.USER); } } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMultiCursorModifier.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleMultiCursorModifier.ts index 214f40e3a53..a2022660076 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleMultiCursorModifier.ts @@ -10,8 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export class ToggleMultiCursorModifierAction extends Action { @@ -23,21 +22,16 @@ export class ToggleMultiCursorModifierAction extends Action { constructor( id: string, label: string, - @IConfigurationService private configurationService: IConfigurationService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IConfigurationService private configurationService: IConfigurationService ) { super(id, label); - - this.enabled = !!this.configurationService && !!this.configurationEditingService; } public run(): TPromise { const editorConf = this.configurationService.getConfiguration<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); const newValue: 'ctrlCmd' | 'alt' = (editorConf.multiCursorModifier === 'ctrlCmd' ? 'alt' : 'ctrlCmd'); - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleMultiCursorModifierAction.multiCursorModifierConfigurationKey, value: newValue }); - - return TPromise.as(null); + return this.configurationService.updateValue(ToggleMultiCursorModifierAction.multiCursorModifierConfigurationKey, newValue, ConfigurationTarget.USER); } } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts index dc51edcbf3d..5eff950044a 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @editorAction export class ToggleRenderControlCharacterAction extends EditorAction { @@ -22,10 +22,10 @@ export class ToggleRenderControlCharacterAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void { - const configurationEditingService = accessor.get(IConfigurationEditingService); + const configurationService = accessor.get(IConfigurationService); let newRenderControlCharacters = !editor.getConfiguration().viewInfo.renderControlCharacters; - configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: 'editor.renderControlCharacters', value: newRenderControlCharacters }); + configurationService.updateValue('editor.renderControlCharacters', newRenderControlCharacters, ConfigurationTarget.USER); } } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts index 1ce47241da5..c95eca4cc60 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @editorAction export class ToggleRenderWhitespaceAction extends EditorAction { @@ -22,7 +22,7 @@ export class ToggleRenderWhitespaceAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void { - const configurationEditingService = accessor.get(IConfigurationEditingService); + const configurationService = accessor.get(IConfigurationService); let renderWhitespace = editor.getConfiguration().viewInfo.renderWhitespace; let newRenderWhitespace: string; @@ -32,6 +32,6 @@ export class ToggleRenderWhitespaceAction extends EditorAction { newRenderWhitespace = 'none'; } - configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: 'editor.renderWhitespace', value: newRenderWhitespace }); + configurationService.updateValue('editor.renderWhitespace', newRenderWhitespace, ConfigurationTarget.USER); } } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts b/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts index 83b7176dcbc..e2e99c99949 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/wordWrapMigration.ts @@ -83,7 +83,7 @@ class WordWrapMigrationController extends Disposable implements IEditorContribut } WordWrapMigrationController._checked = true; - let result = this.configurationService.lookup('editor.wrappingColumn'); + let result = this.configurationService.inspect('editor.wrappingColumn'); if (typeof result.value === 'undefined') { // Setting is not used return; diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 346461ba287..81af57279a2 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -54,8 +54,8 @@ export class StartDebugActionItem extends EventEmitter implements IActionItem { } private registerListeners(): void { - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - if (e.sourceConfig.launch) { + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('launch')) { this.updateOptions(); } })); diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index 5818d30bd3b..3c68f376e9c 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -93,7 +93,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi private registerListeners(): void { this.toUnbind.push(this.debugService.onDidChangeState(state => this.update(state))); - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.update(this.debugService.state))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(() => this.update(this.debugService.state))); this.toUnbind.push(this.actionBar.actionRunner.addListener(EventType.RUN, (e: any) => { // check for error if (e.error && !errors.isPromiseCanceledError(e.error)) { diff --git a/src/vs/workbench/parts/debug/browser/debugStatus.ts b/src/vs/workbench/parts/debug/browser/debugStatus.ts index 9e575478b01..58ad52104db 100644 --- a/src/vs/workbench/parts/debug/browser/debugStatus.ts +++ b/src/vs/workbench/parts/debug/browser/debugStatus.ts @@ -43,7 +43,9 @@ export class DebugStatus extends Themable implements IStatusbarItem { protected updateStyles(): void { super.updateStyles(); - this.icon.style.backgroundColor = this.getColor(STATUS_BAR_FOREGROUND); + if (this.icon) { + this.icon.style.backgroundColor = this.getColor(STATUS_BAR_FOREGROUND); + } } public render(container: HTMLElement): IDisposable { @@ -54,8 +56,9 @@ export class DebugStatus extends Themable implements IStatusbarItem { this.quickOpenService.show('debug ').done(undefined, errors.onUnexpectedError); })); statusBarItem.title = nls.localize('debug', "Debug"); - this.icon = dom.append(statusBarItem, $('.icon')); - this.label = dom.append(statusBarItem, $('span.label')); + const a = dom.append(statusBarItem, $('a')); + this.icon = dom.append(a, $('.icon')); + this.label = dom.append(a, $('span.label')); this.setLabel(); this.updateStyles(); } diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index 6b93bdeaf8b..af6801d50c5 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -110,7 +110,7 @@ export class DebugViewlet extends PersistentViewsViewlet { export class FocusVariablesViewAction extends Action { static ID = 'workbench.debug.action.focusVariablesView'; - static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusVariablesView' }, 'Focus Variables'); + static LABEL = nls.localize('debugFocusVariablesView', 'Focus Variables'); constructor(id: string, label: string, @IViewletService private viewletService: IViewletService diff --git a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css index 69bf974a45f..2796a460a0d 100644 --- a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css @@ -103,8 +103,8 @@ } /* Debug status */ -.monaco-workbench .part.statusbar .debug-statusbar-item { - cursor: pointer; +/* A very precise css rule to overwrite the display set in statusbar.css */ +.monaco-workbench > .part.statusbar > .statusbar-item > .debug-statusbar-item > a:not([disabled]):not(.disabled) { display: flex; } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index f8b008d29c0..24ee1c34060 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -387,6 +387,7 @@ export interface IRawAdapter extends IRawEnvAdapter { export interface IDebugConfigurationProvider { type: string; + handle: number; resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): TPromise; provideDebugConfigurations?(folderUri: uri | undefined): TPromise; } @@ -415,7 +416,7 @@ export interface IConfigurationManager { registerDebugConfigurationProvider(handle: number, debugConfigurationProvider: IDebugConfigurationProvider): void; unregisterDebugConfigurationProvider(handle: number): void; - resolveDebugConfiguration(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): TPromise; + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): TPromise; } export interface ILaunch { @@ -590,11 +591,6 @@ export interface IDebugService { */ startDebugging(root: IWorkspaceFolder, configOrName?: IConfig | string, noDebug?: boolean): TPromise; - /** - * Creates a new debug process. Depending on the configuration will either 'launch' or 'attach'. - */ - createProcess(root: IWorkspaceFolder, config: IConfig): TPromise; - /** * Find process by ID. */ diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 2859af8fc30..c8ef0226454 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -916,7 +916,6 @@ export class Model implements IModel { bp.verified = false; } }); - this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable); this.functionBreakpoints.forEach(fbp => fbp.enabled = enable); this._onDidChangeBreakpoints.fire(); 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 a88503d3faf..0b2665666de 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -6,7 +6,7 @@ import 'vs/css!../browser/media/debug.contribution'; import 'vs/css!../browser/media/debugHover'; import * as nls from 'vs/nls'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -132,7 +132,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(PauseAction, PauseActi registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5 }, CONTEXT_NOT_IN_DEBUG_MODE), 'Debug: Start Without Debugging', debugCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_X) } }, CONTEXT_NOT_IN_DEBUG_MODE), 'Debug: Start Without Debugging', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 4d2ba8ef44d..78cecbd98bf 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -213,7 +213,7 @@ export class ConfigurationManager implements IConfigurationManager { private _selectedLaunch: ILaunch; private toDispose: IDisposable[]; private _onDidSelectConfigurationName = new Emitter(); - private _providers: Map; + private providers: IDebugConfigurationProvider[]; constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService, @@ -228,7 +228,7 @@ export class ConfigurationManager implements IConfigurationManager { @IStorageService private storageService: IStorageService, @ILifecycleService lifecycleService: ILifecycleService ) { - this._providers = new Map(); + this.providers = []; this.adapters = []; this.toDispose = []; this.registerListeners(lifecycleService); @@ -242,7 +242,10 @@ export class ConfigurationManager implements IConfigurationManager { if (!debugConfigurationProvider) { return; } - this._providers.set(handle, debugConfigurationProvider); + + debugConfigurationProvider.handle = handle; + this.providers = this.providers.filter(p => p.handle !== handle); + this.providers.push(debugConfigurationProvider); const adapter = this.getAdapter(debugConfigurationProvider.type); // Check if the provider contributes provideDebugConfigurations method if (adapter && debugConfigurationProvider.provideDebugConfigurations) { @@ -250,22 +253,13 @@ export class ConfigurationManager implements IConfigurationManager { } } - public unregisterDebugConfigurationProvider(handle: number): boolean { - return this._providers.delete(handle); + public unregisterDebugConfigurationProvider(handle: number): void { + this.providers = this.providers.filter(p => p.handle !== handle); } - public resolveDebugConfiguration(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): TPromise { - - // collect all candidates - const providers: IDebugConfigurationProvider[] = []; - this._providers.forEach(provider => { - if (provider.type === type && provider.resolveDebugConfiguration) { - providers.push(provider); - } - }); - + public resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): TPromise { // pipe the config through the promises sequentially - return providers.reduce((promise, provider) => { + return this.providers.filter(p => p.type === type && p.resolveDebugConfiguration).reduce((promise, provider) => { return promise.then(config => { if (config) { return provider.resolveDebugConfiguration(folderUri, config); @@ -277,19 +271,8 @@ export class ConfigurationManager implements IConfigurationManager { } public provideDebugConfigurations(folderUri: uri | undefined, type: string): TPromise { - - // collect all candidates - const configs: TPromise[] = []; - this._providers.forEach(provider => { - if (provider.type === type && provider.provideDebugConfigurations) { - configs.push(provider.provideDebugConfigurations(folderUri)); - } - }); - - // combine all configs into one array - return TPromise.join(configs).then(results => { - return [].concat.apply([], results); - }); + return TPromise.join(this.providers.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations(folderUri))) + .then(results => results.reduce((first, second) => first.concat(second), [])); } private registerListeners(lifecycleService: ILifecycleService): void { @@ -340,7 +323,7 @@ export class ConfigurationManager implements IConfigurationManager { this.initLaunches(); this.selectConfiguration(); })); - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(() => { + this.toDispose.push(this.configurationService.onDidChangeConfiguration(() => { this.selectConfiguration(); })); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index a47e5a09be6..d3d1386f87f 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -651,7 +651,7 @@ export class DebugService implements debug.IDebugService { public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug = false, topCompoundName?: string): TPromise { // make sure to save all files and that the configuration is up to date - return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration().then(() => + return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(root).then(() => this.extensionService.onReady().then(() => { if (this.model.getProcesses().length === 0) { this.removeReplExpressions(); @@ -701,13 +701,26 @@ export class DebugService implements debug.IDebugService { config.noDebug = true; } - return this.configurationManager.resolveDebugConfiguration(launch ? launch.workspace.uri : undefined, type, config).then(config => { - // a falsy config indicates an aborted launch - if (config && config.type) { - return this.createProcess(root, config); + const sessionId = generateUuid(); + this.updateStateAndEmit(sessionId, debug.State.Initializing); + const wrapUpState = () => { + if (this.sessionStates.get(sessionId) === debug.State.Initializing) { + this.updateStateAndEmit(sessionId, debug.State.Inactive); } + }; - return undefined; // ignore weird compile error + return (type ? TPromise.as(null) : this.configurationManager.guessAdapter().then(a => type = a && a.type)).then(() => + this.configurationManager.resolveConfigurationByProviders(launch ? launch.workspace.uri : undefined, type, config).then(config => { + // a falsy config indicates an aborted launch + if (config && config.type) { + return this.createProcess(root, config, sessionId); + } + + return launch.openConfigFile(false, type); // cast to ignore weird compile error + }) + ).then(() => wrapUpState(), (err) => { + wrapUpState(); + return err; }); }) ))); @@ -722,7 +735,7 @@ export class DebugService implements debug.IDebugService { return null; } - public createProcess(root: IWorkspaceFolder, config: debug.IConfig): TPromise { + private createProcess(root: IWorkspaceFolder, config: debug.IConfig, sessionId: string): TPromise { return this.textFileService.saveAll().then(() => (this.configurationManager.selectedLaunch ? this.configurationManager.selectedLaunch.resolveConfiguration(config) : TPromise.as(config)).then(resolvedConfig => { if (!resolvedConfig) { @@ -744,9 +757,6 @@ export class DebugService implements debug.IDebugService { return TPromise.wrapError(errors.create(message, { actions: [this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL), CloseAction] })); } - const sessionId = generateUuid(); - this.updateStateAndEmit(sessionId, debug.State.Initializing); - return this.runPreLaunchTask(root, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => { const errorCount = resolvedConfig.preLaunchTask ? this.markerService.getStatistics().errors : 0; const successExitCode = taskSummary && taskSummary.exitCode === 0; @@ -770,7 +780,6 @@ export class DebugService implements debug.IDebugService { }); return undefined; }, (err: TaskError) => { - this.updateStateAndEmit(sessionId, debug.State.Inactive); this.messageService.show(err.severity, { message: err.message, actions: [ @@ -995,7 +1004,7 @@ export class DebugService implements debug.IDebugService { config.noDebug = process.configuration.noDebug; } config.__restart = restartData; - this.createProcess(process.session.root, config).then(() => c(null), err => e(err)); + this.createProcess(process.session.root, config, process.getId()).then(() => c(null), err => e(err)); }, 300); }); }).then(() => { @@ -1110,7 +1119,8 @@ export class DebugService implements debug.IDebugService { const breakpointsToSend = this.model.getBreakpoints().filter(bp => this.model.areBreakpointsActivated() && bp.enabled && bp.uri.toString() === modelUri.toString()); const source = process.sources.get(modelUri.toString()); - const rawSource = source ? source.raw : { path: modelUri.scheme === 'file' || modelUri.scheme === debug.DEBUG_SCHEME ? paths.normalize(modelUri.fsPath, true) : modelUri.toString(), name: resources.basenameOrAuthority(modelUri) }; + const path = modelUri.scheme === 'file' || modelUri.scheme === debug.DEBUG_SCHEME ? paths.normalize(modelUri.fsPath, true) : modelUri.toString(); + const rawSource = source ? source.raw : { path: decodeURIComponent(path), name: resources.basenameOrAuthority(modelUri) }; if (breakpointsToSend.length && !rawSource.adapterData) { rawSource.adapterData = breakpointsToSend[0].adapterData; } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts index d8104fbc3a2..bc97bf770ba 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts @@ -282,6 +282,9 @@ export class CallStackController extends BaseDebugController { return element.source.uri.toString(); } + if (element instanceof Thread) { + return element.threadId; + } } // user clicked / pressed on 'Load More Stack Frames', get those stack frames and refresh the tree. @@ -1062,11 +1065,12 @@ export class BreakpointsActionProvider implements IActionProvider { } public getSecondaryActions(tree: ITree, element: any): TPromise { - const actions: IAction[] = []; - - if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) { - actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL)); + if (element instanceof ExceptionBreakpoint) { + return TPromise.as([]); } + + const actions: IAction[] = []; + actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL)); if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length > 1) { actions.push(this.instantiationService.createInstance(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL)); actions.push(new Separator()); diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/parts/debug/test/common/mockDebug.ts index 975950318de..c36e51331c9 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebug.ts @@ -92,10 +92,6 @@ export class MockDebugService implements debug.IDebugService { return TPromise.as(null); } - public createProcess(root: IWorkspaceFolder, config: debug.IConfig): TPromise { - return TPromise.as(null); - } - public findProcessByUUID(uuid: string): debug.IProcess | null { return null; } diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index 1217632d2ab..e32066bcf7a 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -709,7 +709,7 @@ export class ToggleAutoUpdateAction extends Action { ) { super(id, label, '', true); this.updateEnablement(); - configurationService.onDidUpdateConfiguration(() => this.updateEnablement()); + configurationService.onDidChangeConfiguration(() => this.updateEnablement()); } private updateEnablement(): void { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index a365c3fe77d..c6f10cffca4 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -23,8 +23,7 @@ import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace } from 'vs/platf import { Schemas } from 'vs/base/common/network'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as pfs from 'vs/base/node/pfs'; import * as os from 'os'; @@ -59,7 +58,6 @@ export class ExtensionTipsService implements IExtensionTipsService { @IFileService private fileService: IFileService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IConfigurationService private configurationService: IConfigurationService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, @IMessageService private messageService: IMessageService, @ITelemetryService private telemetryService: ITelemetryService ) { @@ -449,9 +447,7 @@ export class ExtensionTipsService implements IExtensionTipsService { } private setIgnoreRecommendationsConfig(configVal: boolean) { - let target = ConfigurationTarget.USER; - const configKey = 'extensions.ignoreRecommendations'; - this.configurationEditingService.writeConfiguration(target, { key: configKey, value: configVal }); + this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); if (configVal) { const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index ccbee9dd3a1..6a9c5ce9481 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -111,7 +111,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens this.disposables.push(viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables)); this.isAutoUpdateEnabled = this.extensionsWorkbenchService.isAutoUpdateEnabled; - this.configurationService.onDidUpdateConfiguration(() => { + this.configurationService.onDidChangeConfiguration(() => { const isAutoUpdateEnabled = this.extensionsWorkbenchService.isAutoUpdateEnabled; if (this.isAutoUpdateEnabled !== isAutoUpdateEnabled) { this.isAutoUpdateEnabled = isAutoUpdateEnabled; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index 481df3dfc08..eaa33074295 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -253,13 +253,15 @@ export class ExtensionsListView extends ViewsViewletPanel { }); if (names.length) { - const namesOptions = assign({}, options, { names }); + const namesOptions = assign({}, options, { names, source: 'extRegex' }); pagerPromises.push(this.extensionsWorkbenchService.queryGallery(namesOptions)); } } if (text) { options = assign(options, { text: text.substr(0, 350) }); + } else { + options.source = 'viewlet'; } pagerPromises.push(this.extensionsWorkbenchService.queryGallery(options)); diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 91edacaad0a..7808d2d4c4c 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -24,9 +24,8 @@ import { } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IChoiceService, IMessageService } from 'vs/platform/message/common/message'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; @@ -323,7 +322,6 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { @IExtensionManagementService private extensionService: IExtensionManagementService, @IExtensionGalleryService private galleryService: IExtensionGalleryService, @IConfigurationService private configurationService: IConfigurationService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, @ITelemetryService private telemetryService: ITelemetryService, @IMessageService private messageService: IMessageService, @IChoiceService private choiceService: IChoiceService, @@ -349,7 +347,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { .on(this.onOpenExtensionUrl, this, this.disposables); this._isAutoUpdateEnabled = this.configurationService.getConfiguration(ConfigurationKey).autoUpdate; - this.configurationService.onDidUpdateConfiguration(() => { + this.configurationService.onDidChangeConfiguration(() => { const isAutoUpdateEnabled = this.configurationService.getConfiguration(ConfigurationKey).autoUpdate; if (this._isAutoUpdateEnabled !== isAutoUpdateEnabled) { this._isAutoUpdateEnabled = isAutoUpdateEnabled; @@ -464,7 +462,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { if (this.isAutoUpdateEnabled === autoUpdate) { return TPromise.as(null); } - return this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: 'extensions.autoUpdate', value: autoUpdate }); + return this.configurationService.updateValue('extensions.autoUpdate', autoUpdate, ConfigurationTarget.USER); } private eventuallySyncWithGallery(immediate = false): void { diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index 405be3a135f..17f180eedc1 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -53,7 +53,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); - instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, getConfiguration: () => ({}) }); + instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, onDidChangeConfiguration: () => { }, getConfiguration: () => ({}) }); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 3cfe083cd16..2fad5c275f0 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -56,7 +56,7 @@ suite('ExtensionsWorkbenchService Test', () => { instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); - instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, getConfiguration: () => ({}) }); + instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, onDidChangeConfiguration: () => { }, getConfiguration: () => ({}) }); instantiationService.stub(IExtensionManagementService, ExtensionManagementService); instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); diff --git a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts index 4b70527ce55..b60b3bce030 100644 --- a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts @@ -24,7 +24,6 @@ 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 { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CancelAction } from 'vs/platform/message/common/message'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -47,7 +46,6 @@ export class TextFileEditor extends BaseTextEditor { @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @IHistoryService private historyService: IHistoryService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, @@ -87,16 +85,6 @@ export class TextFileEditor extends BaseTextEditor { public setInput(input: FileEditorInput, options?: EditorOptions): TPromise { - // We have a current input in this editor and are about to either open a new editor or jump to a different - // selection inside the editor. Thus we store the current selection into the navigation history so that - // a user can navigate back to the exact position he left off. - if (this.input) { - const selection = this.getControl().getSelection(); - if (selection) { - this.historyService.add(this.input, { startLineNumber: selection.startLineNumber, startColumn: selection.startColumn }); - } - } - // Return early for same input unless we force to open const forceOpen = options && options.forceOpen; if (!forceOpen && input.matches(this.input)) { diff --git a/src/vs/workbench/parts/files/browser/explorerViewlet.ts b/src/vs/workbench/parts/files/browser/explorerViewlet.ts index 44cac3254e1..8855798cefa 100644 --- a/src/vs/workbench/parts/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/browser/explorerViewlet.ts @@ -14,7 +14,6 @@ import { Builder } from 'vs/base/browser/builder'; import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, OpenEditorsVisibleCondition } from 'vs/workbench/parts/files/common/files'; import { PersistentViewsViewlet, ViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ActionRunner, FileViewletState } from 'vs/workbench/parts/files/browser/views/explorerViewer'; import { ExplorerView, IExplorerViewOptions } from 'vs/workbench/parts/files/browser/views/explorerView'; import { EmptyView } from 'vs/workbench/parts/files/browser/views/emptyView'; @@ -24,11 +23,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -52,7 +50,6 @@ export class ExplorerViewlet extends PersistentViewsViewlet { @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService protected instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService @@ -65,7 +62,7 @@ export class ExplorerViewlet extends PersistentViewsViewlet { this.registerViews(); this.onConfigurationUpdated(); - this._register(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated())); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated())); this._register(this.contextService.onDidChangeWorkspaceName(e => this.updateTitleArea())); this._register(this.contextService.onDidChangeWorkbenchState(() => this.registerViews())); this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.registerViews())); diff --git a/src/vs/workbench/parts/files/browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/browser/fileActions.contribution.ts index aa587300cc4..478d1d9ef84 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.contribution.ts @@ -65,7 +65,7 @@ class FilesViewerActionContributor extends ActionBarContributor { } // Directory Actions - if (stat.isDirectory && stat.exists) { + if (stat.isDirectory && !stat.nonexistentRoot) { // New File actions.push(this.instantiationService.createInstance(NewFileAction, tree, stat)); @@ -91,7 +91,8 @@ class FilesViewerActionContributor extends ActionBarContributor { actions.push(new Separator(null, 100)); } - if (stat.isRoot && this.environmentService.appQuality !== 'stable') { + // Workspace Root Folder Actions + if (stat.isRoot) { const addRootFolderAction: Action = this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL); addRootFolderAction.order = 52; actions.push(addRootFolderAction); @@ -189,33 +190,33 @@ actionBarRegistry.registerActionBarContributor(Scope.VIEWER, FilesViewerActionCo actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewersActionContributor); // Contribute Global Actions -const category = nls.localize('filesCategory', "Files"); +const category = nls.localize('filesCategory', "File"); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCopyPathAction, GlobalCopyPathAction.ID, GlobalCopyPathAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_P) }), 'Files: Copy Path of Active File', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_S }), 'Files: Save', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: void 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'Files: Save All', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFilesAction, SaveFilesAction.ID, SaveFilesAction.LABEL), 'Files: Save All Files', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL), 'Files: Revert File', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewFileAction, GlobalNewFileAction.ID, GlobalNewFileAction.LABEL), 'Files: New File', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewFolderAction, GlobalNewFolderAction.ID, GlobalNewFolderAction.LABEL), 'Files: New Folder', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'Files: Compare Active File With...', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusOpenEditorsView, FocusOpenEditorsView.ID, FocusOpenEditorsView.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_E) }), 'Files: Focus on Open Editors View', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFilesExplorer, FocusFilesExplorer.ID, FocusFilesExplorer.LABEL), 'Files: Focus on Files Explorer', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'Files: Reveal Active File in Side Bar', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'Files: Collapse Folders in Explorer', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), 'Files: Refresh Explorer', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S }), 'Files: Save As...', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'Files: New Untitled File', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRevealInOSAction, GlobalRevealInOSAction.ID, GlobalRevealInOSAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R) }), 'Files: Reveal Active File', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'Files: Open Active File in New Window', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithSavedAction, CompareWithSavedAction.ID, CompareWithSavedAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D) }), 'Files: Compare Active File with Saved', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCopyPathAction, GlobalCopyPathAction.ID, GlobalCopyPathAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_P) }), 'File: Copy Path of Active File', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_S }), 'File: Save', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: void 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFilesAction, SaveFilesAction.ID, SaveFilesAction.LABEL), 'File: Save All Files', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL), 'File: Revert File', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewFileAction, GlobalNewFileAction.ID, GlobalNewFileAction.LABEL), 'File: New File', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewFolderAction, GlobalNewFolderAction.ID, GlobalNewFolderAction.LABEL), 'File: New Folder', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'File: Compare Active File With...', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(FocusOpenEditorsView, FocusOpenEditorsView.ID, FocusOpenEditorsView.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_E) }), 'File: Focus on Open Editors View', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFilesExplorer, FocusFilesExplorer.ID, FocusFilesExplorer.LABEL), 'File: Focus on Files Explorer', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'File: Reveal Active File in Side Bar', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'File: Collapse Folders in Explorer', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), 'File: Refresh Explorer', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S }), 'File: Save As...', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'File: New Untitled File', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRevealInOSAction, GlobalRevealInOSAction.ID, GlobalRevealInOSAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R) }), 'File: Reveal Active File', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'File: Open Active File in New Window', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithSavedAction, CompareWithSavedAction.ID, CompareWithSavedAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D) }), 'File: Compare Active File with Saved', category); if (isMacintosh) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'Files: Open...', category); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', category); } else { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'Files: Open File...', category); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'Files: Open Folder...', category); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', category); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', category); } // Commands diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 8f9afccbafe..c56c5a48fa7 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -27,7 +27,7 @@ import { VIEWLET_ID, FileOnDiskContentProvider } from 'vs/workbench/parts/files/ import labels = require('vs/base/common/labels'); import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { toResource, IEditorIdentifier, EditorInput } from 'vs/workbench/common/editor'; +import { toResource, IEditorIdentifier } from 'vs/workbench/common/editor'; import { FileStat, Model, NewStatPlaceholder } from 'vs/workbench/parts/files/common/explorerModel'; import { ExplorerView } from 'vs/workbench/parts/files/browser/views/explorerView'; import { ExplorerViewlet } from 'vs/workbench/parts/files/browser/explorerViewlet'; @@ -35,10 +35,9 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { IQuickOpenService, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { Position, IResourceInput, IEditorInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor'; +import { Position, IResourceInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature2, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, IMessageWithAction, IConfirmation, Severity, CancelAction, IConfirmationResult } from 'vs/platform/message/common/message'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -50,8 +49,8 @@ import { withFocusedFilesExplorer, revealInOSCommand, revealInExplorerCommand, c import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { once } from 'vs/base/common/event'; export interface IEditableData { action: IAction; @@ -641,8 +640,7 @@ export class BaseDeleteFileAction extends BaseFileAction { @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService, - @IConfigurationService private configurationService: IConfigurationService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IConfigurationService private configurationService: IConfigurationService ) { super(id, label, fileService, messageService, textFileService); @@ -714,7 +712,7 @@ export class BaseDeleteFileAction extends BaseFileAction { let confirmPromise: TPromise; // Check if we need to ask for confirmation at all - if (this.skipConfirm || (this.useTrash && this.configurationService.lookup(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY).value === false)) { + if (this.skipConfirm || (this.useTrash && this.configurationService.getValue(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY) === false)) { confirmPromise = TPromise.as({ confirmed: true } as IConfirmationResult); } @@ -746,7 +744,7 @@ export class BaseDeleteFileAction extends BaseFileAction { // Check for confirmation checkbox let updateConfirmSettingsPromise: TPromise = TPromise.as(void 0); if (confirmation.checkboxChecked === true) { - updateConfirmSettingsPromise = this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY, value: false }); + updateConfirmSettingsPromise = this.configurationService.updateValue(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); } return updateConfirmSettingsPromise.then(() => { @@ -792,10 +790,9 @@ export class MoveFileToTrashAction extends BaseDeleteFileAction { @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService, - @IConfigurationService configurationService: IConfigurationService, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService + @IConfigurationService configurationService: IConfigurationService ) { - super(MoveFileToTrashAction.ID, nls.localize('delete', "Delete"), tree, element, true, fileService, messageService, textFileService, configurationService, configurationEditingService); + super(MoveFileToTrashAction.ID, nls.localize('delete', "Delete"), tree, element, true, fileService, messageService, textFileService, configurationService); } } @@ -1197,12 +1194,9 @@ export class GlobalCompareResourcesAction extends Action { id: string, label: string, @IQuickOpenService private quickOpenService: IQuickOpenService, - @IInstantiationService private instantiationService: IInstantiationService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IHistoryService private historyService: IHistoryService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, @IMessageService private messageService: IMessageService, - @IEnvironmentService private environmentService: IEnvironmentService + @IEditorGroupService private editorGroupService: IEditorGroupService ) { super(id, label); } @@ -1212,50 +1206,22 @@ export class GlobalCompareResourcesAction extends Action { const activeResource = activeInput ? activeInput.getResource() : void 0; if (activeResource) { - // Keep as resource to compare - globalResourceToCompare = activeResource; - - // Pick another entry from history - interface IHistoryPickEntry extends IFilePickOpenEntry { - input: IEditorInput | IResourceInput; - } - - const history = this.historyService.getHistory(); - const picks: IHistoryPickEntry[] = history.map(input => { - let resource: URI; - let label: string; - let description: string; - - if (input instanceof EditorInput) { - resource = input.getResource(); - } else { - resource = (input as IResourceInput).resource; + // Compare with next editor that opens + const unbind = once(this.editorGroupService.onEditorOpening)(e => { + const resource = e.input.getResource(); + if (resource) { + e.prevent(() => { + return this.editorService.openEditor({ + leftResource: activeResource, + rightResource: resource + }); + }); } + }); - // Cannot compare file with self - exclude active file - if (!!resource && resource.toString() === globalResourceToCompare.toString()) { - return void 0; - } - - if (!resource) { - return void 0; // only support to compare with files and untitled - } - - label = paths.basename(resource.fsPath); - description = labels.getPathLabel(resources.dirname(resource), this.contextService, this.environmentService); - - return { input, resource, label, description }; - }).filter(p => !!p); - - 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()) { - compareAction.run().done(() => compareAction.dispose()); - } else { - this.messageService.show(Severity.Info, nls.localize('unableToFileToCompare', "The selected file can not be compared with '{0}'.", paths.basename(globalResourceToCompare.fsPath))); - } - } + // Bring up quick open + this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } }).then(() => { + unbind.dispose(); // make sure to unbind if quick open is closing }); } else { this.messageService.show(Severity.Info, nls.localize('openFileToCompare', "Open a file first to compare it with another file.")); @@ -1305,7 +1271,7 @@ export class CompareResourcesAction extends Action { return nls.localize('compareFiles', "Compare Files"); } - _isEnabled(): boolean { + public _isEnabled(): boolean { // Need at least a resource to compare if (!globalResourceToCompare) { diff --git a/src/vs/workbench/parts/files/browser/files.contribution.ts b/src/vs/workbench/parts/files/browser/files.contribution.ts index 173b92b32bd..878dea1810b 100644 --- a/src/vs/workbench/parts/files/browser/files.contribution.ts +++ b/src/vs/workbench/parts/files/browser/files.contribution.ts @@ -350,6 +350,16 @@ configurationRegistry.registerConfiguration({ nls.localize('sortOrder.modified', 'Files and folders are sorted by last modified date, in descending order. Folders are displayed before files.') ], 'description': nls.localize({ key: 'sortOrder', comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'] }, "Controls sorting order of files and folders in the explorer. In addition to the default sorting, you can set the order to 'mixed' (files and folders sorted combined), 'type' (by file type), 'modified' (by last modified date) or 'filesFirst' (sort files before folders).") - } + }, + 'explorer.decorations.colors': { + type: 'boolean', + description: nls.localize('explorer.decorations.colors', "Controls if file decorations should use colors."), + default: true + }, + 'explorer.decorations.badges': { + type: 'boolean', + description: nls.localize('explorer.decorations.badges', "Controls if file decorations should use badges."), + default: true + }, } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css index 749714d7782..4ef2be5ab15 100644 --- a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css @@ -106,6 +106,10 @@ background-image: url("collapsed-hc.svg"); } +.explorer-folders-view.hide-arrows .monaco-tree-row .content::before { + display: none; +} + .explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row:hover > .content .monaco-action-bar, .explorer-viewlet .explorer-open-editors .monaco-tree.focused .monaco-tree-row.focused > .content .monaco-action-bar, .explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content.dirty > .monaco-action-bar { diff --git a/src/vs/workbench/parts/files/browser/views/explorerView.ts b/src/vs/workbench/parts/files/browser/views/explorerView.ts index eb9f3dbdc9b..2c2d45cb9c6 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerView.ts @@ -33,7 +33,7 @@ import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService } from 'vs/platform/progress/common/progress'; @@ -120,7 +120,7 @@ export class ExplorerView extends ViewsViewletPanel { private getFileEventsExcludes(root?: URI): glob.IExpression { const scope = root ? { resource: root } : void 0; - const configuration = this.configurationService.getConfiguration(undefined, scope); + const configuration = this.configurationService.getConfiguration(scope); return (configuration && configuration.files && configuration.files.exclude) || Object.create(null); } @@ -161,6 +161,7 @@ export class ExplorerView extends ViewsViewletPanel { const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { DOM.toggleClass(this.treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); + DOM.toggleClass(this.treeContainer, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); }; this.disposables.push(this.themeService.onDidFileIconThemeChange(onFileIconThemeChange)); @@ -203,7 +204,7 @@ export class ExplorerView extends ViewsViewletPanel { this.disposables.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); // Also handle configuration updates - this.disposables.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration(), true))); + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration(), e))); }); } @@ -250,7 +251,7 @@ export class ExplorerView extends ViewsViewletPanel { } } - private onConfigurationUpdated(configuration: IFilesConfiguration, refresh?: boolean): void { + private onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): void { if (this.isDisposed) { return; // guard against possible race condition when config change causes recreate of views } @@ -269,8 +270,13 @@ export class ExplorerView extends ViewsViewletPanel { needsRefresh = true; } - // Refresh viewer as needed - if (refresh && needsRefresh) { + if (event && !needsRefresh) { + needsRefresh = event.affectsConfiguration('explorer.decorations.colors') + || event.affectsConfiguration('explorer.decorations.badges'); + } + + // Refresh viewer as needed if this originates from a config event + if (event && needsRefresh) { this.doRefresh().done(null, errors.onUnexpectedError); } } @@ -764,33 +770,33 @@ export class ExplorerView extends ViewsViewletPanel { } // Load Root Stat with given target path configured - const promise = TPromise.join(targetsToResolve.map((target, index) => this.fileService.resolveFile(target.resource, target.options).then(result => { - // Convert to model - const modelStat = FileStat.create(result, target.root, target.options.resolveTo); - // Subsequent refresh: Merge stat into our local model and refresh tree - FileStat.mergeLocalWithDisk(modelStat, this.model.roots[index]); + const promise = TPromise.join(targetsToResolve.map((target, index) => this.fileService.resolveFile(target.resource, target.options) + .then(result => FileStat.create(result, target.root, target.options.resolveTo), err => FileStat.create({ + resource: target.resource, + name: resources.basenameOrAuthority(target.resource), + mtime: 0, + etag: undefined, + isDirectory: true, + hasChildren: false + }, target.root)) + .then(modelStat => { + // Subsequent refresh: Merge stat into our local model and refresh tree + FileStat.mergeLocalWithDisk(modelStat, this.model.roots[index]); - const input = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? this.model.roots[0] : this.model; - let statsToExpand: FileStat[] = this.explorerViewer.getExpandedElements().concat(targetsToExpand.map(target => this.model.findClosest(target))); - if (input === this.explorerViewer.getInput()) { - return this.explorerViewer.refresh().then(() => sequence(statsToExpand.map(e => () => this.explorerViewer.expand(e)))); - } + const input = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? this.model.roots[0] : this.model; + let statsToExpand: FileStat[] = this.explorerViewer.getExpandedElements().concat(targetsToExpand.map(target => this.model.findClosest(target))); + if (input === this.explorerViewer.getInput()) { + return this.explorerViewer.refresh().then(() => sequence(statsToExpand.map(e => () => this.explorerViewer.expand(e)))); + } - // Display roots only when multi folder workspace - // Make sure to expand all folders that where expanded in the previous session - if (input === this.model) { - // We have transitioned into workspace view -> expand all roots - statsToExpand = this.model.roots.concat(statsToExpand); - } - return this.explorerViewer.setInput(input).then(() => sequence(statsToExpand.map(e => () => this.explorerViewer.expand(e)))); - }, e => FileStat.create({ - resource: target.resource, - name: resources.basenameOrAuthority(target.resource), - mtime: 0, - etag: undefined, - isDirectory: true, - hasChildren: false - }, target.root)))); + // Display roots only when multi folder workspace + // Make sure to expand all folders that where expanded in the previous session + if (input === this.model) { + // We have transitioned into workspace view -> expand all roots + statsToExpand = this.model.roots.concat(statsToExpand); + } + return this.explorerViewer.setInput(input).then(() => sequence(statsToExpand.map(e => () => this.explorerViewer.expand(e)))); + }))); this.progressService.showWhile(promise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */); diff --git a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts index d60aa33d4d8..0328d3ef7ea 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts @@ -37,7 +37,7 @@ import { DragMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -57,7 +57,6 @@ import { distinct } from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { getPathLabel } from 'vs/base/common/labels'; import { extractResources } from 'vs/base/browser/dnd'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; export class FileDataSource implements IDataSource { constructor( @@ -108,14 +107,8 @@ export class FileDataSource implements IDataSource { return stat.children; }, (e: any) => { - stat.exists = false; stat.hasChildren = false; - if (!stat.isRoot) { - this.messageService.show(Severity.Error, e); - } else { - // We render the roots that do not exist differently, nned to do a refresh - tree.refresh(stat, false); - } + this.messageService.show(Severity.Error, e); return []; // we could not resolve any children because of an error }); @@ -290,7 +283,8 @@ export class FileRenderer implements IRenderer { state: FileViewletState, @IContextViewService private contextViewService: IContextViewService, @IInstantiationService private instantiationService: IInstantiationService, - @IThemeService private themeService: IThemeService + @IThemeService private themeService: IThemeService, + @IConfigurationService private configurationService: IConfigurationService ) { this.state = state; } @@ -318,12 +312,18 @@ export class FileRenderer implements IRenderer { // File Label if (!editableData) { - templateData.label.element.style.display = 'block'; + templateData.label.element.style.display = 'flex'; const extraClasses = ['explorer-item']; - if (!stat.exists && stat.isRoot) { + if (stat.nonexistentRoot) { extraClasses.push('nonexistent-root'); } - templateData.label.setFile(stat.resource, { hidePath: true, fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE, extraClasses }); + templateData.label.setFile(stat.resource, { + hidePath: true, + title: stat.nonexistentRoot ? nls.localize('canNotResolve', "Can not resolve folder {0}", stat.resource.toString()) : undefined, + fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE, + extraClasses, + fileDecorations: this.configurationService.getConfiguration().explorer.decorations + }); } // Input Box @@ -568,7 +568,7 @@ export class FileSorter implements ISorter { } private registerListeners(): void { - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); } private onConfigurationUpdated(configuration: IFilesConfiguration): void { @@ -688,7 +688,7 @@ export class FileFilter implements IFilter { public updateConfiguration(): boolean { let needsRefresh = false; this.contextService.getWorkspace().folders.forEach(folder => { - const configuration = this.configurationService.getConfiguration(undefined, { resource: folder.uri }); + const configuration = this.configurationService.getConfiguration({ resource: folder.uri }); const excludesConfig = (configuration && configuration.files && configuration.files.exclude) || Object.create(null); needsRefresh = needsRefresh || !objects.equals(this.hiddenExpressionPerRoot.get(folder.uri.toString()), excludesConfig); this.hiddenExpressionPerRoot.set(folder.uri.toString(), objects.clone(excludesConfig)); // do not keep the config, as it gets mutated under our hoods @@ -745,8 +745,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { @IBackupFileService private backupFileService: IBackupFileService, @IWindowService private windowService: IWindowService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @IEnvironmentService private environmentService: IEnvironmentService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService + @IEnvironmentService private environmentService: IEnvironmentService ) { super(stat => this.statToResource(stat)); @@ -770,7 +769,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { } private registerListeners(): void { - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); } private onConfigurationUpdated(config: IFilesConfiguration): void { @@ -910,10 +909,6 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { // Handle folders by adding to workspace if we are in workspace context const folders = result.filter(result => result.stat.isDirectory).map(result => result.stat.resource); if (folders.length > 0) { - if (this.environmentService.appQuality === 'stable') { - return void 0; // TODO@Ben multi root - } - if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { return this.workspaceEditingService.addFolders(folders); } @@ -952,7 +947,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { let confirmPromise: TPromise; // Handle confirm setting - const confirmDragAndDrop = !isCopy && this.configurationService.lookup(FileDragAndDrop.CONFIRM_DND_SETTING_KEY).value; + const confirmDragAndDrop = !isCopy && this.configurationService.getValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY); if (confirmDragAndDrop) { confirmPromise = this.messageService.confirm({ message: nls.localize('confirmMove', "Are you sure you want to move '{0}'?", source.name), @@ -970,7 +965,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { // Check for confirmation checkbox let updateConfirmSettingsPromise: TPromise = TPromise.as(void 0); if (confirmation.checkboxChecked === true) { - updateConfirmSettingsPromise = this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: FileDragAndDrop.CONFIRM_DND_SETTING_KEY, value: false }); + updateConfirmSettingsPromise = this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false, ConfigurationTarget.USER); } return updateConfirmSettingsPromise.then(() => { diff --git a/src/vs/workbench/parts/files/browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/browser/views/openEditorsView.ts index 51b5ab51ba9..301fe2a0081 100644 --- a/src/vs/workbench/parts/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/browser/views/openEditorsView.ts @@ -14,7 +14,7 @@ import { IItemCollapseEvent } from 'vs/base/parts/tree/browser/treeModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup } from 'vs/workbench/common/editor'; import { SaveAllAction } from 'vs/workbench/parts/files/browser/fileActions'; @@ -183,7 +183,7 @@ export class OpenEditorsView extends ViewsViewletPanel { this.disposables.push(this.model.onModelChanged(e => this.onEditorStacksModelChanged(e))); // Also handle configuration updates - this.disposables.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration(), e))); // Handle dirty counter this.disposables.push(this.untitledEditorService.onDidChangeDirty(e => this.updateDirtyIndicator())); @@ -259,7 +259,7 @@ export class OpenEditorsView extends ViewsViewletPanel { } } - private onConfigurationUpdated(configuration: IFilesConfiguration): void { + private onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): void { if (this.isDisposed) { return; // guard against possible race condition when config change causes recreate of views } @@ -280,6 +280,11 @@ export class OpenEditorsView extends ViewsViewletPanel { // Adjust expanded body size this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(this.model); + + // Trigger a 'repaint' when decoration settings change + if (event && event.affectsConfiguration('explorer.decorations')) { + this.tree.refresh(); + } } private updateDirtyIndicator(): void { diff --git a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts index a67a4d87ea6..c72ae86cbaa 100644 --- a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts @@ -24,13 +24,14 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor'; import { OpenEditor } from 'vs/workbench/parts/files/common/explorerModel'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files'; +import { explorerItemToFileResource, IFilesConfiguration } from 'vs/workbench/parts/files/common/files'; import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorStacksModel, EditorGroup } from 'vs/workbench/common/editor/editorStacksModel'; import { SaveFileAction, RevertFileAction, SaveFileAsAction, OpenToSideAction, SelectResourceForCompareAction, CompareResourcesAction, SaveAllInGroupAction, CompareWithSavedAction } from 'vs/workbench/parts/files/browser/fileActions'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { CloseOtherEditorsInGroupAction, CloseEditorAction, CloseEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const $ = dom.$; @@ -86,7 +87,8 @@ export class Renderer implements IRenderer { constructor( private actionProvider: ActionProvider, @IInstantiationService private instantiationService: IInstantiationService, - @IKeybindingService private keybindingService: IKeybindingService + @IKeybindingService private keybindingService: IKeybindingService, + @IConfigurationService private configurationService: IConfigurationService ) { // noop } @@ -149,7 +151,11 @@ export class Renderer implements IRenderer { private renderOpenEditor(tree: ITree, editor: OpenEditor, templateData: IOpenEditorTemplateData): void { editor.isDirty() ? dom.addClass(templateData.container, 'dirty') : dom.removeClass(templateData.container, 'dirty'); - templateData.root.setEditor(editor.editorInput, { italic: editor.isPreview(), extraClasses: ['open-editor'] }); + templateData.root.setEditor(editor.editorInput, { + italic: editor.isPreview(), + extraClasses: ['open-editor'], + fileDecorations: this.configurationService.getConfiguration().explorer.decorations + }); templateData.actionBar.context = { group: editor.editorGroup, editor: editor.editorInput }; } diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index f84e67a1176..c6ac6973969 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -24,6 +24,7 @@ import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUt import { Verbosity } from 'vs/platform/editor/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; /** * A file editor input is the input type for the file editor of file system resources. @@ -47,7 +48,8 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { @IWorkspaceContextService private contextService: IWorkspaceContextService, @ITextFileService private textFileService: ITextFileService, @IEnvironmentService private environmentService: IEnvironmentService, - @ITextModelService private textModelResolverService: ITextModelService + @ITextModelService private textModelResolverService: ITextModelService, + @IHashService private hashService: IHashService ) { super(); @@ -274,7 +276,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { public getTelemetryDescriptor(): object { const descriptor = super.getTelemetryDescriptor(); - descriptor['resource'] = telemetryURIDescriptor(this.getResource()); + descriptor['resource'] = telemetryURIDescriptor(this.getResource(), path => this.hashService.createSHA1(path)); /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/common/editors/fileEditorTracker.ts index c07ed76b795..3209fbc2a2e 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorTracker.ts @@ -75,7 +75,7 @@ export class FileEditorTracker implements IWorkbenchContribution { this.lifecycleService.onShutdown(this.dispose, this); // Configuration - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); } private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index fe3198bbf79..e20b9c1921e 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -82,7 +82,6 @@ export class FileStat implements IFileStat { public children: FileStat[]; public parent: FileStat; - public exists: boolean; public isDirectoryResolved: boolean; constructor(resource: URI, public root: FileStat, isDirectory?: boolean, hasChildren?: boolean, name: string = paths.basename(resource.fsPath), mtime?: number, etag?: string) { @@ -102,7 +101,10 @@ export class FileStat implements IFileStat { } this.isDirectoryResolved = false; - this.exists = true; + } + + public get nonexistentRoot(): boolean { + return this.isRoot && !this.isDirectoryResolved; } public getId(): string { diff --git a/src/vs/workbench/parts/files/common/files.ts b/src/vs/workbench/parts/files/common/files.ts index f2a423c3ce9..e63b3e56b59 100644 --- a/src/vs/workbench/parts/files/common/files.ts +++ b/src/vs/workbench/parts/files/common/files.ts @@ -71,6 +71,10 @@ export interface IFilesConfiguration extends IFilesConfiguration, IWorkbenchEdit enableDragAndDrop: boolean; confirmDelete: boolean; sortOrder: SortOrder; + decorations: { + colors: boolean; + badges: boolean; + }; }; editor: IEditorOptions; } @@ -178,4 +182,4 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider { public dispose(): void { this.fileWatcher = dispose(this.fileWatcher); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/html/common/htmlInput.ts b/src/vs/workbench/parts/html/common/htmlInput.ts index fe8955dc0c7..8338d4fd5b6 100644 --- a/src/vs/workbench/parts/html/common/htmlInput.ts +++ b/src/vs/workbench/parts/html/common/htmlInput.ts @@ -7,6 +7,7 @@ import URI from 'vs/base/common/uri'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; export interface HtmlInputOptions { @@ -25,8 +26,9 @@ export class HtmlInput extends ResourceEditorInput { description: string, resource: URI, public readonly options: HtmlInputOptions, - @ITextModelService textModelResolverService: ITextModelService + @ITextModelService textModelResolverService: ITextModelService, + @IHashService hashService: IHashService ) { - super(name, description, resource, textModelResolverService); + super(name, description, resource, textModelResolverService, hashService); } } diff --git a/src/vs/workbench/parts/markers/browser/markersFileDecorations.ts b/src/vs/workbench/parts/markers/browser/markersFileDecorations.ts new file mode 100644 index 00000000000..9dd0ce444fb --- /dev/null +++ b/src/vs/workbench/parts/markers/browser/markersFileDecorations.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IMarkerService, IMarker } from 'vs/platform/markers/common/markers'; +import { IDecorationsService, IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import URI from 'vs/base/common/uri'; +import Event from 'vs/base/common/event'; +import { localize } from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import Severity from 'vs/base/common/severity'; +import { editorErrorForeground, editorWarningForeground } from 'vs/editor/common/view/editorColorRegistry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; + +class MarkersDecorationsProvider implements IDecorationsProvider { + + readonly label: string = localize('label', "Problems"); + readonly onDidChange: Event; + + constructor( + private readonly _markerService: IMarkerService + ) { + this.onDidChange = _markerService.onMarkerChanged; + } + + provideDecorations(resource: URI): IDecorationData { + let markers = this._markerService.read({ resource }); + let first: IMarker; + for (const marker of markers) { + if (!first || marker.severity > first.severity) { + first = marker; + } + } + + if (!first) { + return undefined; + } + + return { + weight: 100 * first.severity, + bubble: true, + title: markers.length === 1 ? localize('tooltip.1', "1 problem in this file") : localize('tooltip.N', "{0} problems in this file", markers.length), + letter: markers.length.toString(), + color: first.severity === Severity.Error ? editorErrorForeground : editorWarningForeground, + }; + } +} + +class MarkersFileDecorations implements IWorkbenchContribution { + + private readonly _disposables: IDisposable[]; + private _provider: IDisposable; + private _enabled: boolean; + + constructor( + @IMarkerService private _markerService: IMarkerService, + @IDecorationsService private _decorationsService: IDecorationsService, + @IConfigurationService private _configurationService: IConfigurationService + ) { + // + this._disposables = [ + this._configurationService.onDidChangeConfiguration(this._updateEnablement, this), + ]; + this._updateEnablement(); + } + + dispose(): void { + dispose(this._provider); + dispose(this._disposables); + } + + getId(): string { + return 'markers.MarkersFileDecorations'; + } + + private _updateEnablement(): void { + let value = this._configurationService.getConfiguration<{ decorations: { enabled: boolean } }>('problems'); + if (value.decorations.enabled === this._enabled) { + return; + } + this._enabled = value.decorations.enabled; + if (this._enabled) { + const provider = new MarkersDecorationsProvider(this._markerService); + this._provider = this._decorationsService.registerDecorationsProvider(provider); + } else if (this._provider) { + this._enabled = value.decorations.enabled; + this._provider.dispose(); + } + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(MarkersFileDecorations); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + 'id': 'problems', + 'order': 101, + 'type': 'object', + 'properties': { + 'problems.decorations.enabled': { + 'description': localize('markers.showOnFile', "Show Errors & Warnings on files and folder."), + 'type': 'boolean', + 'default': false + } + } +}); diff --git a/src/vs/workbench/parts/markers/browser/markersPanel.ts b/src/vs/workbench/parts/markers/browser/markersPanel.ts index a4060b88693..13305518c4e 100644 --- a/src/vs/workbench/parts/markers/browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/browser/markersPanel.ts @@ -253,7 +253,7 @@ export class MarkersPanel extends Panel { } private createListeners(): void { - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationsUpdated(this.configurationService.getConfiguration()))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationsUpdated(this.configurationService.getConfiguration()))); this.toUnbind.push(this.markerService.onMarkerChanged(this.onMarkerChanged, this)); this.toUnbind.push(this.editorGroupService.onEditorsChanged(this.onEditorsChanged, this)); this.toUnbind.push(this.tree.addListener('selection', () => this.onSelected())); diff --git a/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts b/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts index d6b4f686ffd..39970c806b9 100644 --- a/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts +++ b/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts @@ -48,6 +48,7 @@ export function registerContributions(): void { } }); + // markers panel Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( MarkersPanel, @@ -66,4 +67,4 @@ export function registerContributions(): void { // Retaining old action to show errors and warnings, so that custom bindings to this action for existing users works. registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleErrorsAndWarningsAction, ToggleErrorsAndWarningsAction.ID, ToggleErrorsAndWarningsAction.LABEL), 'Show Errors and Warnings'); -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/markers/common/markersModel.ts b/src/vs/workbench/parts/markers/common/markersModel.ts index 561705c5a60..735d45e845b 100644 --- a/src/vs/workbench/parts/markers/common/markersModel.ts +++ b/src/vs/workbench/parts/markers/common/markersModel.ts @@ -58,11 +58,14 @@ export class Marker { } public toString(): string { - return [`file: '${this.marker.resource}'`, - `severity: '${Severity.toString(this.marker.severity)}'`, - `message: '${this.marker.message}'`, - `at: '${this.marker.startLineNumber},${this.marker.startColumn}'`, - `source: '${this.marker.source ? this.marker.source : ''}'`].join('\n'); + return [ + `file: '${this.marker.resource}'`, + `severity: '${Severity.toString(this.marker.severity)}'`, + `message: '${this.marker.message}'`, + `at: '${this.marker.startLineNumber},${this.marker.startColumn}'`, + `source: '${this.marker.source ? this.marker.source : ''}'`, + `code: '${this.marker.code ? this.marker.code : ''}'` + ].join('\n'); } } diff --git a/src/vs/workbench/parts/markers/markers.contribution.ts b/src/vs/workbench/parts/markers/markers.contribution.ts index 8e93a5abe41..b7b91dbc852 100644 --- a/src/vs/workbench/parts/markers/markers.contribution.ts +++ b/src/vs/workbench/parts/markers/markers.contribution.ts @@ -5,5 +5,7 @@ import { registerContributions } from 'vs/workbench/parts/markers/browser/markersWorkbenchContributions'; import { registerContributions as registerElectronContributions } from 'vs/workbench/parts/markers/electron-browser/markersElectronContributions'; +import './browser/markersFileDecorations'; + registerContributions(); -registerElectronContributions(); \ No newline at end of file +registerElectronContributions(); diff --git a/src/vs/workbench/parts/markers/test/common/markersModel.test.ts b/src/vs/workbench/parts/markers/test/common/markersModel.test.ts index 450eb2d2ff9..eacdf1fcc13 100644 --- a/src/vs/workbench/parts/markers/test/common/markersModel.test.ts +++ b/src/vs/workbench/parts/markers/test/common/markersModel.test.ts @@ -121,10 +121,12 @@ suite('MarkersModel Test', () => { }); test('toString()', function () { - assert.equal(`file: 'file:///a/res1'\nseverity: 'Error'\nmessage: 'some message'\nat: '10,5'\nsource: 'tslint'`, new Marker('', aMarker('a/res1')).toString()); - assert.equal(`file: 'file:///a/res2'\nseverity: 'Warning'\nmessage: 'some message'\nat: '10,5'\nsource: 'tslint'`, new Marker('', aMarker('a/res2', Severity.Warning)).toString()); - assert.equal(`file: 'file:///a/res2'\nseverity: 'Info'\nmessage: 'Info'\nat: '1,2'\nsource: ''`, new Marker('', aMarker('a/res2', Severity.Info, 1, 2, 1, 8, 'Info', '')).toString()); - assert.equal(`file: 'file:///a/res2'\nseverity: ''\nmessage: 'Ignore message'\nat: '1,2'\nsource: 'Ignore'`, new Marker('', aMarker('a/res2', Severity.Ignore, 1, 2, 1, 8, 'Ignore message', 'Ignore')).toString()); + const res1Marker = aMarker('a/res1'); + res1Marker.code = '1234'; + assert.equal(`file: 'file:///a/res1'\nseverity: 'Error'\nmessage: 'some message'\nat: '10,5'\nsource: 'tslint'\ncode: '1234'`, new Marker('', res1Marker).toString()); + assert.equal(`file: 'file:///a/res2'\nseverity: 'Warning'\nmessage: 'some message'\nat: '10,5'\nsource: 'tslint'\ncode: ''`, new Marker('', aMarker('a/res2', Severity.Warning)).toString()); + assert.equal(`file: 'file:///a/res2'\nseverity: 'Info'\nmessage: 'Info'\nat: '1,2'\nsource: ''\ncode: ''`, new Marker('', aMarker('a/res2', Severity.Info, 1, 2, 1, 8, 'Info', '')).toString()); + assert.equal(`file: 'file:///a/res2'\nseverity: ''\nmessage: 'Ignore message'\nat: '1,2'\nsource: 'Ignore'\ncode: ''`, new Marker('', aMarker('a/res2', Severity.Ignore, 1, 2, 1, 8, 'Ignore message', 'Ignore')).toString()); }); function hasMarker(markers: Marker[], marker: IMarker): boolean { diff --git a/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts index a2007eb7622..d24ed6250c3 100644 --- a/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/browser/preferences.contribution.ts @@ -24,7 +24,7 @@ import { import { PreferencesService } from 'vs/workbench/parts/preferences/browser/preferencesService'; import { IInstantiationService, ServicesAccessor } 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'; +import { PreferencesContribution } from 'vs/workbench/parts/preferences/common/preferencesContribution'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -257,7 +257,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PreferencesContentProvider); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PreferencesContribution); CommandsRegistry.registerCommand(OPEN_FOLDER_SETTINGS_COMMAND, function (accessor: ServicesAccessor, args?: IWorkspaceFolder) { const preferencesService = accessor.get(IPreferencesService); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 411b295653f..910dcb09831 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -39,7 +39,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { VSash } from 'vs/base/browser/ui/sash/sash'; import { Widget } from 'vs/base/browser/ui/widget'; @@ -58,6 +57,9 @@ import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import Event, { Emitter } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; +import { MessageController } from 'vs/editor/contrib/message/messageController'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; export class PreferencesEditorInput extends SideBySideEditorInput { public static ID: string = 'workbench.editorinputs.preferencesEditorInput'; @@ -74,9 +76,10 @@ export class PreferencesEditorInput extends SideBySideEditorInput { export class DefaultPreferencesEditorInput extends ResourceEditorInput { public static ID = 'workbench.editorinputs.defaultpreferences'; constructor(defaultSettingsResource: URI, - @ITextModelService textModelResolverService: ITextModelService + @ITextModelService textModelResolverService: ITextModelService, + @IHashService hashService: IHashService ) { - super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService); + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService, hashService); } getTypeId(): string { @@ -247,7 +250,7 @@ export class PreferencesEditor extends BaseEditor { } if (this.workspaceContextService.getWorkspaceFolder(resource)) { - return ConfigurationTarget.FOLDER; + return ConfigurationTarget.WORKSPACE_FOLDER; } return null; @@ -722,7 +725,19 @@ export class DefaultPreferencesEditor extends BaseTextEditor { } public createEditorControl(parent: Builder, configuration: IEditorOptions): editorCommon.IEditor { - return this.instantiationService.createInstance(DefaultPreferencesCodeEditor, parent.getHTMLElement(), configuration); + const editor = this.instantiationService.createInstance(DefaultPreferencesCodeEditor, parent.getHTMLElement(), configuration); + + // Inform user about editor being readonly if user starts type + this.toUnbind.push(editor.onDidType(() => this.onDidType(editor))); + + return editor; + } + + private onDidType(editor: editorCommon.ICommonCodeEditor): void { + const messageController = MessageController.get(editor); + if (!messageController.isVisible()) { + messageController.showMessage(nls.localize('defaultEditorReadonly', "Edit in the right hand side editor to override defaults."), editor.getSelection().getPosition()); + } } protected getConfigurationOverrides(): IEditorOptions { @@ -905,7 +920,7 @@ class SettingsEditorContribution extends AbstractSettingsEditorContribution impl return this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); case ConfigurationTarget.WORKSPACE: return this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); - case ConfigurationTarget.FOLDER: + case ConfigurationTarget.WORKSPACE_FOLDER: return this.instantiationService.createInstance(FolderSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); } } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index d74dd56da46..151446902f8 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -22,9 +22,7 @@ import { IContextMenuService, ContextSubMenu } from 'vs/platform/contextview/bro import { SettingsGroupTitleWidget, EditPreferenceWidget, SettingsHeaderWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RangeHighlightDecorations } from 'vs/workbench/common/editor/rangeDecorations'; -import { IConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { overrideIdentifierFromKey } from 'vs/platform/configuration/common/model'; import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; @@ -33,6 +31,7 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorE import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { overrideIdentifierFromKey, IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export interface IPreferencesRenderer extends IDisposable { preferencesModel: IPreferencesEditorModel; @@ -72,7 +71,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend @IPreferencesService protected preferencesService: IPreferencesService, @ITelemetryService private telemetryService: ITelemetryService, @ITextFileService private textFileService: ITextFileService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, + @IConfigurationService private configurationService: IConfigurationService, @IMessageService private messageService: IMessageService, @IInstantiationService protected instantiationService: IInstantiationService ) { @@ -116,19 +115,8 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [key] }); const overrideIdentifier = source.overrideOf ? overrideIdentifierFromKey(source.overrideOf.key) : null; const resource = this.preferencesModel.uri; - this.configurationEditingService.writeConfiguration(this.preferencesModel.configurationTarget, { key, value }, { donotSave: this.textFileService.isDirty(resource), donotNotifyError: true, scopes: { overrideIdentifier, resource } }) - .then(() => this.onSettingUpdated(source), error => { - this.messageService.show(Severity.Error, this.toErrorMessage(error, this.preferencesModel.configurationTarget)); - }); - } - - private toErrorMessage(error: ConfigurationEditingError, target: ConfigurationTarget): string { - switch (error.code) { - case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION: { - return nls.localize('errorInvalidConfiguration', "Unable to write into settings. Correct errors/warnings in the file and try again."); - }; - } - return error.message; + this.configurationService.updateValue(key, value, { overrideIdentifier, resource }, this.preferencesModel.configurationTarget) + .then(() => this.onSettingUpdated(source)); } private onModelChanged(): void { @@ -192,11 +180,11 @@ export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements I @IPreferencesService preferencesService: IPreferencesService, @ITelemetryService telemetryService: ITelemetryService, @ITextFileService textFileService: ITextFileService, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService, + @IConfigurationService configurationService: IConfigurationService, @IMessageService messageService: IMessageService, @IInstantiationService instantiationService: IInstantiationService ) { - super(editor, preferencesModel, associatedPreferencesModel, preferencesService, telemetryService, textFileService, configurationEditingService, messageService, instantiationService); + super(editor, preferencesModel, associatedPreferencesModel, preferencesService, telemetryService, textFileService, configurationService, messageService, instantiationService); this.untrustedSettingRenderer = this._register(instantiationService.createInstance(UnsupportedWorkspaceSettingsRenderer, editor, preferencesModel)); this.workspaceConfigurationRenderer = this._register(instantiationService.createInstance(WorkspaceConfigurationRenderer, editor, preferencesModel)); } @@ -220,11 +208,11 @@ export class FolderSettingsRenderer extends UserSettingsRenderer implements IPre @IPreferencesService preferencesService: IPreferencesService, @ITelemetryService telemetryService: ITelemetryService, @ITextFileService textFileService: ITextFileService, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService, + @IConfigurationService configurationService: IConfigurationService, @IMessageService messageService: IMessageService, @IInstantiationService instantiationService: IInstantiationService ) { - super(editor, preferencesModel, associatedPreferencesModel, preferencesService, telemetryService, textFileService, configurationEditingService, messageService, instantiationService); + super(editor, preferencesModel, associatedPreferencesModel, preferencesService, telemetryService, textFileService, configurationService, messageService, instantiationService); this.unsupportedWorkbenchSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedWorkbenchSettingsRenderer, editor, preferencesModel)); } @@ -413,7 +401,7 @@ class DefaultSettingsHeaderRenderer extends Disposable { public render(settingsGroups: ISettingsGroup[]) { if (settingsGroups.length) { - this.settingsHeaderWidget.setMessage(''); + this.settingsHeaderWidget.setMessage(nls.localize('defaultSettings', "Place your settings in the right hand side editor to override.")); } else { this.settingsHeaderWidget.setMessage(nls.localize('noSettingsFound', "No Settings Found.")); } @@ -563,6 +551,7 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, className: 'findMatch' } + }; } @@ -808,7 +797,7 @@ class EditSettingRenderer extends Disposable { return true; } if (configurationNode.type === 'boolean' || configurationNode.enum) { - if ((this.masterSettingsModel).configurationTarget !== ConfigurationTarget.FOLDER) { + if ((this.masterSettingsModel).configurationTarget !== ConfigurationTarget.WORKSPACE_FOLDER) { return true; } if (configurationNode.scope === ConfigurationScope.RESOURCE) { @@ -961,7 +950,7 @@ class UnsupportedWorkspaceSettingsRenderer extends Disposable { @IMarkerService private markerService: IMarkerService ) { super(); - this._register(this.configurationService.onDidUpdateConfiguration(() => this.render())); + this._register(this.configurationService.onDidChangeConfiguration(() => this.render())); } private getMarkerMessage(settingKey: string): string { diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts index 5e5597721c6..fdf08f805c2 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts @@ -8,7 +8,6 @@ import * as network from 'vs/base/common/network'; import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import URI from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; import { ResourceMap } from 'vs/base/common/map'; import * as labels from 'vs/base/common/labels'; import * as strings from 'vs/base/common/strings'; @@ -18,7 +17,7 @@ import { EditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { Position as EditorPosition, IEditor } from 'vs/platform/editor/common/editor'; +import { Position as EditorPosition, IEditor, IEditorOptions } from 'vs/platform/editor/common/editor'; import { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -27,8 +26,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 { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IPreferencesService, IPreferencesEditorModel, ISetting, getSettingsTargetName } from 'vs/workbench/parts/preferences/common/preferences'; +import { IPreferencesService, IPreferencesEditorModel, ISetting, getSettingsTargetName, FOLDER_SETTINGS_PATH, DEFAULT_SETTINGS_EDITOR_SETTING } from 'vs/workbench/parts/preferences/common/preferences'; import { SettingsEditorModel, DefaultSettingsEditorModel, DefaultKeybindingsEditorModel, defaultKeybindingsContents, WorkspaceConfigModel } from 'vs/workbench/parts/preferences/common/preferencesModels'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbench/parts/preferences/browser/preferencesEditor'; @@ -41,15 +39,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; - - -interface IWorkbenchSettingsConfiguration { - workbench: { - settings: { - openDefaultSettings: boolean; - } - }; -} +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; const emptyEditableSettingsContent = '{\n}'; @@ -76,7 +66,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic @IEnvironmentService private environmentService: IEnvironmentService, @ITelemetryService private telemetryService: ITelemetryService, @ITextModelService private textModelResolverService: ITextModelService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, @IExtensionService private extensionService: IExtensionService, @IKeybindingService keybindingService: IKeybindingService, @IModelService private modelService: IModelService, @@ -117,7 +106,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } getFolderSettingsResource(resource: URI): URI { - return this.getEditableSettingsURI(ConfigurationTarget.FOLDER, resource); + return this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, resource); } resolveContent(uri: URI): TPromise { @@ -180,26 +169,26 @@ export class PreferencesService extends Disposable implements IPreferencesServic } if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - return this.createEditableSettingsEditorModel(ConfigurationTarget.FOLDER, uri); + return this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE_FOLDER, uri); } return TPromise.wrap>(null); } - openGlobalSettings(): TPromise { - return this.doOpenSettings(ConfigurationTarget.USER, this.userSettingsResource); + openGlobalSettings(options?: IEditorOptions, position?: EditorPosition): TPromise { + return this.doOpenSettings(ConfigurationTarget.USER, this.userSettingsResource, options, position); } - openWorkspaceSettings(): TPromise { + openWorkspaceSettings(options?: IEditorOptions, position?: EditorPosition): TPromise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.messageService.show(Severity.Info, nls.localize('openFolderFirst', "Open a folder first to create workspace settings")); return TPromise.as(null); } - return this.doOpenSettings(ConfigurationTarget.WORKSPACE, this.workspaceSettingsResource); + return this.doOpenSettings(ConfigurationTarget.WORKSPACE, this.workspaceSettingsResource, options, position); } - openFolderSettings(folder: URI): TPromise { - return this.doOpenSettings(ConfigurationTarget.FOLDER, this.getEditableSettingsURI(ConfigurationTarget.FOLDER, folder)); + openFolderSettings(folder: URI, options?: IEditorOptions, position?: EditorPosition): TPromise { + return this.doOpenSettings(ConfigurationTarget.WORKSPACE_FOLDER, this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, folder), options, position); } switchSettings(target: ConfigurationTarget, resource: URI): TPromise { @@ -259,22 +248,28 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI): TPromise { - const openDefaultSettings = !!this.configurationService.getConfiguration().workbench.settings.openDefaultSettings; + private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: IEditorOptions, position?: EditorPosition): TPromise { + const openDefaultSettings = !!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING); return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { + if (!options) { + options = { pinned: true }; + } else { + options.pinned = true; + } + if (openDefaultSettings) { const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget)); const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, editableSettingsEditorInput); this.lastOpenedSettingsInput = preferencesEditorInput; - return this.editorService.openEditor(preferencesEditorInput, { pinned: true }); + return this.editorService.openEditor(preferencesEditorInput, options, position); } - return this.editorService.openEditor(editableSettingsEditorInput, { pinned: true }); + return this.editorService.openEditor(editableSettingsEditorInput, options, position); }); } private getDefaultSettingsResource(configurationTarget: ConfigurationTarget): URI { - if (configurationTarget === ConfigurationTarget.FOLDER) { + if (configurationTarget === ConfigurationTarget.WORKSPACE_FOLDER) { return this.defaultResourceSettingsResource; } return this.defaultSettingsResource; @@ -282,7 +277,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic private getPreferencesEditorInputName(target: ConfigurationTarget, resource: URI): string { const name = getSettingsTargetName(target, resource, this.contextService); - return target === ConfigurationTarget.FOLDER ? nls.localize('folderSettingsName', "{0} (Folder Settings)", name) : name; + return target === ConfigurationTarget.WORKSPACE_FOLDER ? nls.localize('folderSettingsName', "{0} (Folder Settings)", name) : name; } private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): TPromise { @@ -325,10 +320,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } const workspace = this.contextService.getWorkspace(); - return workspace.configuration || workspace.folders[0].toResource(paths.join('.vscode', 'settings.json')); - case ConfigurationTarget.FOLDER: + return workspace.configuration || workspace.folders[0].toResource(FOLDER_SETTINGS_PATH); + case ConfigurationTarget.WORKSPACE_FOLDER: const folder = this.contextService.getWorkspaceFolder(resource); - return folder ? folder.toResource(paths.join('.vscode', 'settings.json')) : null; + return folder ? folder.toResource(FOLDER_SETTINGS_PATH) : null; } return null; } @@ -393,7 +388,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } return { lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 }; } - return this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: languageKey, value: {} }, { donotSave: true }) + return this.configurationService.updateValue(languageKey, {}, ConfigurationTarget.USER) .then(() => { setting = settingsModel.getPreference(languageKey); let content = eol + this.spaces(2, configuration) + eol + this.spaces(1, configuration); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts index f2df5ec870c..81cc7dbdf4f 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts @@ -32,9 +32,9 @@ import { ISelectBoxStyles, defaultStyles } from 'vs/base/browser/ui/selectBox/se import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { Color } from 'vs/base/common/color'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export class SettingsHeaderWidget extends Widget implements IViewZone { @@ -313,7 +313,7 @@ export class SettingsTargetsWidget extends Widget { private updateLabel(): void { this.targetLabel.textContent = getSettingsTargetName(this._configuartionTarget, this._uri, this.workspaceContextService); - const details = ConfigurationTarget.FOLDER === this._configuartionTarget ? localize('folderSettingsDetails', "Folder Settings") : ''; + const details = ConfigurationTarget.WORKSPACE_FOLDER === this._configuartionTarget ? localize('folderSettingsDetails', "Folder Settings") : ''; this.targetDetails.textContent = details; DOM.toggleClass(this.targetDetails, 'empty', !details); } @@ -358,7 +358,7 @@ export class SettingsTargetsWidget extends Widget { actions.push(...workspaceFolders.map((folder, index) => { return { id: 'folderSettingsTarget' + index, - label: getSettingsTargetName(ConfigurationTarget.FOLDER, folder.uri, this.workspaceContextService), + label: getSettingsTargetName(ConfigurationTarget.WORKSPACE_FOLDER, folder.uri, this.workspaceContextService), checked: this._uri.toString() === folder.uri.toString(), enabled: true, run: () => this.onTargetClicked(folder.uri) diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index 1b7200927c9..4bd6f7acdef 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -8,11 +8,12 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IEditor } from 'vs/platform/editor/common/editor'; +import { IEditor, Position, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IKeybindingItemEntry } from 'vs/workbench/parts/preferences/common/keybindingsEditorModel'; import { IRange } from 'vs/editor/common/core/range'; -import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { join } from 'vs/base/common/paths'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export interface ISettingsGroup { id: string; @@ -76,9 +77,9 @@ export interface IPreferencesService { resolveContent(uri: URI): TPromise; createPreferencesEditorModel(uri: URI): TPromise>; - openGlobalSettings(): TPromise; - openWorkspaceSettings(): TPromise; - openFolderSettings(folder: URI): TPromise; + openGlobalSettings(options?: IEditorOptions, position?: Position): TPromise; + openWorkspaceSettings(options?: IEditorOptions, position?: Position): TPromise; + openFolderSettings(folder: URI, options?: IEditorOptions, position?: Position): TPromise; switchSettings(target: ConfigurationTarget, resource: URI): TPromise; openGlobalKeybindingSettings(textual: boolean): TPromise; @@ -106,10 +107,11 @@ export function getSettingsTargetName(target: ConfigurationTarget, resource: URI return localize('userSettingsTarget', "User Settings"); case ConfigurationTarget.WORKSPACE: return localize('workspaceSettingsTarget', "Workspace Settings"); - case ConfigurationTarget.FOLDER: + case ConfigurationTarget.WORKSPACE_FOLDER: const folder = workspaceContextService.getWorkspaceFolder(resource); return folder ? folder.name : ''; } + return ''; } export const CONTEXT_SETTINGS_EDITOR = new RawContextKey('inSettingsEditor', false); @@ -130,4 +132,7 @@ export const KEYBINDINGS_EDITOR_COMMAND_REMOVE = 'keybindings.editor.removeKeybi export const KEYBINDINGS_EDITOR_COMMAND_RESET = 'keybindings.editor.resetKeybinding'; export const KEYBINDINGS_EDITOR_COMMAND_COPY = 'keybindings.editor.copyKeybindingEntry'; export const KEYBINDINGS_EDITOR_COMMAND_SHOW_CONFLICTS = 'keybindings.editor.showConflicts'; -export const KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS = 'keybindings.editor.focusKeybindings'; \ No newline at end of file +export const KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS = 'keybindings.editor.focusKeybindings'; + +export const FOLDER_SETTINGS_PATH = join('.vscode', 'settings.json'); +export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings'; \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/common/preferencesContentProvider.ts b/src/vs/workbench/parts/preferences/common/preferencesContentProvider.ts deleted file mode 100644 index 33141c96052..00000000000 --- a/src/vs/workbench/parts/preferences/common/preferencesContentProvider.ts +++ /dev/null @@ -1,82 +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 { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import URI from 'vs/base/common/uri'; -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/registry/common/platform'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences'; -import { dispose } from 'vs/base/common/lifecycle'; - -const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); - -export class PreferencesContentProvider implements IWorkbenchContribution { - - constructor( - @IModelService private modelService: IModelService, - @ITextModelService private textModelResolverService: ITextModelService, - @IPreferencesService private preferencesService: IPreferencesService, - @IModeService private modeService: IModeService - ) { - this.start(); - } - - public getId(): string { - return 'vs.contentprovider'; - } - - private start(): void { - - this.textModelResolverService.registerTextModelContentProvider('vscode', { - provideTextContent: (uri: URI): TPromise => { - if (uri.scheme !== 'vscode') { - return null; - } - if (uri.authority === 'schemas') { - const schemaModel = this.getSchemaModel(uri); - if (schemaModel) { - return TPromise.as(schemaModel); - } - } - return this.preferencesService.resolveContent(uri) - .then(content => { - if (content !== null && content !== void 0) { - let mode = this.modeService.getOrCreateMode('json'); - const model = this.modelService.createModel(content, mode, uri); - return TPromise.as(model); - } - return null; - }); - } - }); - } - - private getSchemaModel(uri: URI): IModel { - let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; - if (schema) { - const modelContent = JSON.stringify(schema); - const mode = this.modeService.getOrCreateMode('json'); - const model = this.modelService.createModel(modelContent, mode, uri); - - let disposables = []; - disposables.push(schemaRegistry.onDidChangeSchema(schemaUri => { - if (schemaUri === uri.toString()) { - schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; - model.setValue(JSON.stringify(schema)); - } - })); - disposables.push(model.onWillDispose(() => dispose(disposables))); - - return model; - } - return null; - } -} diff --git a/src/vs/workbench/parts/preferences/common/preferencesContribution.ts b/src/vs/workbench/parts/preferences/common/preferencesContribution.ts new file mode 100644 index 00000000000..db5b54b3fb7 --- /dev/null +++ b/src/vs/workbench/parts/preferences/common/preferencesContribution.ts @@ -0,0 +1,148 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import URI from 'vs/base/common/uri'; +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/registry/common/platform'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IPreferencesService, FOLDER_SETTINGS_PATH, DEFAULT_SETTINGS_EDITOR_SETTING } from 'vs/workbench/parts/preferences/common/preferences'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { endsWith } from 'vs/base/common/strings'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEditorOpeningEvent } from 'vs/workbench/common/editor'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); + +export class PreferencesContribution implements IWorkbenchContribution { + private editorOpeningListener: IDisposable; + private settingsListener: IDisposable; + + constructor( + @IModelService private modelService: IModelService, + @ITextModelService private textModelResolverService: ITextModelService, + @IPreferencesService private preferencesService: IPreferencesService, + @IModeService private modeService: IModeService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IWorkspaceContextService private workspaceService: IWorkspaceContextService, + @IConfigurationService private configurationService: IConfigurationService + ) { + this.settingsListener = this.configurationService.onDidChangeConfiguration(() => this.handleSettingsEditorOverride()); + this.handleSettingsEditorOverride(); + + this.start(); + } + + private handleSettingsEditorOverride(): void { + + // dispose any old listener we had + this.editorOpeningListener = dispose(this.editorOpeningListener); + + // install editor opening listener unless user has disabled this + if (!!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING)) { + this.editorOpeningListener = this.editorGroupService.onEditorOpening(e => this.onEditorOpening(e)); + } + } + + private onEditorOpening(event: IEditorOpeningEvent): void { + const resource = event.input.getResource(); + if ( + !resource || resource.scheme !== 'file' || // require a file path opening + !endsWith(resource.fsPath, 'settings.json') || // file must end in settings.json + !this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING) // user has not disabled default settings editor + ) { + return; + } + + // Global User Settings File + if (resource.fsPath === this.environmentService.appSettingsPath) { + return event.prevent(() => this.preferencesService.openGlobalSettings(event.options, event.position)); + } + + // Single Folder Workspace Settings File + const state = this.workspaceService.getWorkbenchState(); + if (state === WorkbenchState.FOLDER) { + const folders = this.workspaceService.getWorkspace().folders; + if (resource.fsPath === folders[0].toResource(FOLDER_SETTINGS_PATH).fsPath) { + return event.prevent(() => this.preferencesService.openWorkspaceSettings(event.options, event.position)); + } + } + + // Multi Folder Workspace Settings File + else if (state === WorkbenchState.WORKSPACE) { + const folders = this.workspaceService.getWorkspace().folders; + for (let i = 0; i < folders.length; i++) { + if (resource.fsPath === folders[i].toResource(FOLDER_SETTINGS_PATH).fsPath) { + return event.prevent(() => this.preferencesService.openFolderSettings(folders[i].uri, event.options, event.position)); + } + } + } + } + + public getId(): string { + return 'vs.contentprovider'; + } + + private start(): void { + + this.textModelResolverService.registerTextModelContentProvider('vscode', { + provideTextContent: (uri: URI): TPromise => { + if (uri.scheme !== 'vscode') { + return null; + } + if (uri.authority === 'schemas') { + const schemaModel = this.getSchemaModel(uri); + if (schemaModel) { + return TPromise.as(schemaModel); + } + } + return this.preferencesService.resolveContent(uri) + .then(content => { + if (content !== null && content !== void 0) { + let mode = this.modeService.getOrCreateMode('json'); + const model = this.modelService.createModel(content, mode, uri); + return TPromise.as(model); + } + return null; + }); + } + }); + } + + private getSchemaModel(uri: URI): IModel { + let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; + if (schema) { + const modelContent = JSON.stringify(schema); + const mode = this.modeService.getOrCreateMode('json'); + const model = this.modelService.createModel(modelContent, mode, uri); + + let disposables = []; + disposables.push(schemaRegistry.onDidChangeSchema(schemaUri => { + if (schemaUri === uri.toString()) { + schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; + model.setValue(JSON.stringify(schema)); + } + })); + disposables.push(model.onWillDispose(() => dispose(disposables))); + + return model; + } + return null; + } + + public dispose(): void { + this.editorOpeningListener = dispose(this.editorOpeningListener); + this.settingsListener = dispose(this.settingsListener); + } +} diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts index f17231a589b..1233bd69199 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts @@ -18,7 +18,6 @@ import { EditorModel } from 'vs/workbench/common/editor'; import { IConfigurationNode, IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN, IConfigurationPropertySchema, ConfigurationScope } 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 { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { IRange } from 'vs/editor/common/core/range'; @@ -26,6 +25,7 @@ import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/co import { TPromise } from 'vs/base/common/winjs.base'; import { Queue } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; class SettingMatches { diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts index f5e99434096..8b5d93e1438 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts @@ -95,7 +95,7 @@ class CommandsHistory { } private registerListeners(): void { - this.configurationService.onDidUpdateConfiguration(e => this.updateConfiguration()); + this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()); once(this.lifecycleService.onShutdown)(reason => this.save()); } @@ -404,7 +404,7 @@ export class CommandsHandler extends QuickOpenHandler { this.commandsHistory = this.instantiationService.createInstance(CommandsHistory); - this.configurationService.onDidUpdateConfiguration(e => this.updateConfiguration()); + this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()); this.updateConfiguration(); } diff --git a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts b/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts index 854fbca08ae..988464d7332 100644 --- a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts +++ b/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts @@ -58,7 +58,7 @@ export class SettingsChangeRelauncher implements IWorkbenchContribution { } private registerListeners(): void { - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration(), true))); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration(), true))); this.toDispose.push(this.contextService.onDidChangeWorkbenchState(() => setTimeout(() => this.handleWorkbenchState()))); } diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index e29d4a29225..7096a5a7c00 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -9,14 +9,13 @@ import nls = require('vs/nls'); import 'vs/css!./media/dirtydiffDecorator'; import { ThrottledDelayer, always } from 'vs/base/common/async'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; -import { any as anyEvent, filterEvent } from 'vs/base/common/event'; +import Event, { Emitter, any as anyEvent, filterEvent, once } from 'vs/base/common/event'; import * as ext from 'vs/workbench/common/contributions'; -import * as common from 'vs/editor/common/editorCommon'; import { CodeEditor } from 'vs/editor/browser/codeEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMessageService } from 'vs/platform/message/common/message'; +import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -26,79 +25,724 @@ import URI from 'vs/base/common/uri'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { Color, RGBA } from 'vs/base/common/color'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; +import { editorAction, ServicesAccessor, EditorAction, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions'; +import { PeekViewWidget, getOuterEditor } from 'vs/editor/contrib/referenceSearch/browser/peekViewWidget'; +import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Position } from 'vs/editor/common/core/position'; +import { rot } from 'vs/base/common/numbers'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/browser/referencesWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; +import { IActionBarOptions, ActionsOrientation, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { basename } from 'vs/base/common/paths'; +import { MenuId, IMenuService, IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { fillInActions, MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; +import { IChange, ICommonCodeEditor, IEditorModel, ScrollType, IEditorContribution, OverviewRulerLane, IModel } from 'vs/editor/common/editorCommon'; +import { sortedDiff, Splice } from 'vs/base/common/arrays'; + +// TODO@Joao +// Need to subclass MenuItemActionItem in order to respect +// the action context coming from any action bar, without breaking +// existing users +class DiffMenuItemActionItem extends MenuItemActionItem { + + onClick(event: MouseEvent): void { + event.preventDefault(); + event.stopPropagation(); + + this.actionRunner.run(this._commandAction, this._context) + .done(undefined, err => this._messageService.show(Severity.Error, err)); + } +} + +class DiffActionRunner extends ActionRunner { + + runAction(action: IAction, context: any): TPromise { + if (action instanceof MenuItemAction) { + return action.run(...context); + } + + return super.runAction(action, context); + } +} + +export interface IModelRegistry { + getModel(editorModel: IEditorModel): DirtyDiffModel; +} + +export const isDirtyDiffVisible = new RawContextKey('dirtyDiffVisible', false); + +function getChangeHeight(change: IChange): number { + const modified = change.modifiedEndLineNumber - change.modifiedStartLineNumber + 1; + const original = change.originalEndLineNumber - change.originalStartLineNumber + 1; + + if (change.originalEndLineNumber === 0) { + return modified; + } else if (change.modifiedEndLineNumber === 0) { + return original; + } else { + return modified + original; + } +} + +function getModifiedEndLineNumber(change: IChange): number { + if (change.modifiedEndLineNumber === 0) { + return change.modifiedStartLineNumber; + } else { + return change.modifiedEndLineNumber; + } +} + +function getModifiedMiddleLineNumber(change: IChange): number { + if (change.modifiedEndLineNumber === 0) { + return change.modifiedStartLineNumber; + } else { + return Math.round((change.modifiedEndLineNumber + change.modifiedStartLineNumber) / 2); + } +} + +class UIEditorAction extends Action { + + private editor: ICommonCodeEditor; + private action: EditorAction; + private instantiationService: IInstantiationService; + + constructor( + editor: ICommonCodeEditor, + action: EditorAction, + cssClass: string, + @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService + ) { + const keybinding = keybindingService.lookupKeybinding(action.id); + const label = action.label + (keybinding ? ` (${keybinding.getLabel()})` : ''); + + super(action.id, label, cssClass); + + this.instantiationService = instantiationService; + this.action = action; + this.editor = editor; + } + + run(): TPromise { + return TPromise.wrap(this.instantiationService.invokeFunction(accessor => this.action.run(accessor, this.editor, null))); + } +} + +enum ChangeType { + Modify, + Add, + Delete +} + +function getChangeType(change: IChange): ChangeType { + if (change.originalEndLineNumber === 0) { + return ChangeType.Add; + } else if (change.modifiedEndLineNumber === 0) { + return ChangeType.Delete; + } else { + return ChangeType.Modify; + } +} + +function getChangeTypeColor(theme: ITheme, changeType: ChangeType): Color { + switch (changeType) { + case ChangeType.Modify: return theme.getColor(editorGutterModifiedBackground); + case ChangeType.Add: return theme.getColor(editorGutterAddedBackground); + case ChangeType.Delete: return theme.getColor(editorGutterDeletedBackground); + } +} + +class DirtyDiffWidget extends PeekViewWidget { + + private diffEditor: EmbeddedDiffEditorWidget; + private title: string; + private menu: IMenu; + private index: number; + private change: IChange; + private didLayout = false; + private contextKeyService: IContextKeyService; + + constructor( + editor: ICodeEditor, + private model: DirtyDiffModel, + @IThemeService private themeService: IThemeService, + @IInstantiationService private instantiationService: IInstantiationService, + @IMenuService private menuService: IMenuService, + @IKeybindingService private keybindingService: IKeybindingService, + @IMessageService private messageService: IMessageService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(editor, { isResizeable: true, frameWidth: 1 }); + + themeService.onThemeChange(this._applyTheme, this, this._disposables); + this._applyTheme(themeService.getTheme()); + + this.contextKeyService = contextKeyService.createScoped(); + this.contextKeyService.createKey('originalResourceScheme', this.model.original.uri.scheme); + this.menu = menuService.createMenu(MenuId.SCMChangeContext, this.contextKeyService); + + this.create(); + this.title = basename(editor.getModel().uri.fsPath); + this.setTitle(this.title); + + model.onDidChange(this.renderTitle, this, this._disposables); + } + + showChange(index: number): void { + const change = this.model.changes[index]; + this.index = index; + this.change = change; + + const originalModel = this.model.original; + + if (!originalModel) { + return; + } + + const onFirstDiffUpdate = once(this.diffEditor.onDidUpdateDiff); + + // TODO@joao TODO@alex need this setTimeout probably because the + // non-side-by-side diff still hasn't created the view zones + onFirstDiffUpdate(() => setTimeout(() => this.revealChange(change), 0)); + + this.diffEditor.setModel(this.model); + + const position = new Position(getModifiedEndLineNumber(change), 1); + const height = getChangeHeight(change) + /* padding */ 8; + + this.renderTitle(); + + const changeType = getChangeType(change); + const changeTypeColor = getChangeTypeColor(this.themeService.getTheme(), changeType); + this.style({ frameColor: changeTypeColor }); + + this._actionbarWidget.context = [this.model.modified.uri, this.model.changes, index]; + this.show(position, height); + } + + private renderTitle(): void { + const detail = this.model.changes.length > 1 + ? localize('changes', "{0} of {1} changes", this.index + 1, this.model.changes.length) + : localize('change', "{0} of {1} change", this.index + 1, this.model.changes.length); + + this.setTitle(this.title, detail); + } + + protected _fillHead(container: HTMLElement): void { + super._fillHead(container); + + const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'show-previous-change octicon octicon-chevron-up'); + const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'show-next-change octicon octicon-chevron-down'); + + this._disposables.push(previous); + this._disposables.push(next); + this._actionbarWidget.push([previous, next], { label: false, icon: true }); + + const actions: IAction[] = []; + fillInActions(this.menu, { shouldForwardArgs: true }, actions); + this._actionbarWidget.push(actions, { label: false, icon: true }); + } + + protected _getActionBarOptions(): IActionBarOptions { + return { + actionRunner: new DiffActionRunner(), + actionItemProvider: action => this.getActionItem(action), + orientation: ActionsOrientation.HORIZONTAL_REVERSE + }; + } + + getActionItem(action: IAction): IActionItem { + if (!(action instanceof MenuItemAction)) { + return undefined; + } + + return new DiffMenuItemActionItem(action, this.keybindingService, this.messageService); + } + + protected _fillBody(container: HTMLElement): void { + const options: IDiffEditorOptions = { + scrollBeyondLastLine: true, + scrollbar: { + verticalScrollbarSize: 14, + horizontal: 'auto', + useShadows: true, + verticalHasArrows: false, + horizontalHasArrows: false + }, + overviewRulerLanes: 2, + fixedOverflowWidgets: true, + minimap: { enabled: false }, + renderSideBySide: false + }; + + this.diffEditor = this.instantiationService.createInstance(EmbeddedDiffEditorWidget, container, options, this.editor); + } + + protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void { + super._doLayoutBody(heightInPixel, widthInPixel); + this.diffEditor.layout({ height: heightInPixel, width: widthInPixel }); + + if (!this.didLayout) { + this.revealChange(this.change); + this.didLayout = true; + } + } + + private revealChange(change: IChange): void { + const position = new Position(getModifiedMiddleLineNumber(this.change), 1); + this.diffEditor.revealPositionInCenter(position, ScrollType.Immediate); + } + + private _applyTheme(theme: ITheme) { + let borderColor = theme.getColor(peekViewBorder) || Color.transparent; + this.style({ + arrowColor: borderColor, + frameColor: borderColor, + headerBackgroundColor: theme.getColor(peekViewTitleBackground) || Color.transparent, + primaryHeadingColor: theme.getColor(peekViewTitleForeground), + secondaryHeadingColor: theme.getColor(peekViewTitleInfoForeground) + }); + } + + protected revealLine(lineNumber: number) { + this.editor.revealLineInCenterIfOutsideViewport(lineNumber, ScrollType.Smooth); + } +} + +@editorAction +export class ShowPreviousChangeAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.dirtydiff.previous', + label: nls.localize('show previous change', "Show Previous Change"), + alias: 'Show Previous Change', + precondition: ContextKeyExpr.and(EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { kbExpr: EditorContextKeys.textFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_S } + }); + } + + run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void { + const controller = DirtyDiffController.get(editor); + + if (!controller) { + return; + } + + controller.previous(); + } +} + +@editorAction +export class ShowNextChangeAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.dirtydiff.next', + label: nls.localize('show next change', "Show Next Change"), + alias: 'Show Next Change', + precondition: ContextKeyExpr.and(EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { kbExpr: EditorContextKeys.textFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F } + }); + } + + run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void { + const controller = DirtyDiffController.get(editor); + + if (!controller) { + return; + } + + controller.next(); + } +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'closeDirtyDiff', + weight: CommonEditorRegistry.commandWeight(50), + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(isDirtyDiffVisible, ContextKeyExpr.not('config.editor.stablePeek')), + handler: (accessor: ServicesAccessor) => { + const editor = getOuterEditor(accessor); + if (!editor) { + return; + } + + const controller = DirtyDiffController.get(editor); + + if (!controller) { + return; + } + + controller.close(); + } +}); + +@editorContribution +export class DirtyDiffController implements IEditorContribution { + + private static ID = 'editor.contrib.dirtydiff'; + + static get(editor: ICommonCodeEditor): DirtyDiffController { + return editor.getContribution(DirtyDiffController.ID); + } + + modelRegistry: IModelRegistry | null = null; + + private model: DirtyDiffModel | null = null; + private widget: DirtyDiffWidget | null = null; + private currentLineNumber: number = -1; + private currentIndex: number = -1; + private readonly isDirtyDiffVisible: IContextKey; + private session: IDisposable = EmptyDisposable; + private mouseDownInfo: { lineNumber: number } | null = null; + private disposables: IDisposable[] = []; + + constructor( + private editor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService, + @IThemeService private themeService: IThemeService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + this.isDirtyDiffVisible = isDirtyDiffVisible.bindTo(contextKeyService); + this.disposables.push(editor.onMouseDown(e => this.onEditorMouseDown(e))); + this.disposables.push(editor.onMouseUp(e => this.onEditorMouseUp(e))); + } + + getId(): string { + return DirtyDiffController.ID; + } + + next(lineNumber?: number): void { + if (!this.assertWidget()) { + return; + } + + if (typeof lineNumber === 'number' || this.currentIndex === -1) { + this.currentIndex = this.findNextClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber); + } else { + this.currentIndex = rot(this.currentIndex + 1, this.model.changes.length); + } + + const change = this.model.changes[this.currentIndex]; + this.currentLineNumber = change.modifiedStartLineNumber; + + this.widget.showChange(this.currentIndex); + } + + previous(lineNumber?: number): void { + if (!this.assertWidget()) { + return; + } + + if (typeof lineNumber === 'number' || this.currentIndex === -1) { + this.currentIndex = this.findPreviousClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber); + } else { + this.currentIndex = rot(this.currentIndex - 1, this.model.changes.length); + } + + const change = this.model.changes[this.currentIndex]; + this.currentLineNumber = change.modifiedStartLineNumber; + + this.widget.showChange(this.currentIndex); + } + + close(): void { + this.session.dispose(); + this.session = EmptyDisposable; + } + + private assertWidget(): boolean { + if (this.widget) { + if (this.model.changes.length === 0) { + this.close(); + return false; + } + + return true; + } + + if (!this.modelRegistry) { + return false; + } + + const editorModel = this.editor.getModel(); + + if (!editorModel) { + return false; + } + + const model = this.modelRegistry.getModel(editorModel); + + if (!model) { + return false; + } + + if (model.changes.length === 0) { + return false; + } + + this.currentIndex = -1; + this.model = model; + this.widget = this.instantiationService.createInstance(DirtyDiffWidget, this.editor, model); + this.isDirtyDiffVisible.set(true); + + const disposables: IDisposable[] = []; + once(this.widget.onDidClose)(this.close, this, disposables); + model.onDidChange(this.onDidModelChange, this, disposables); + + disposables.push( + this.widget, + toDisposable(() => this.model = this.widget = null), + toDisposable(() => this.isDirtyDiffVisible.set(false)) + ); + + this.session = combinedDisposable(disposables); + return true; + } + + private onDidModelChange(splices: Splice[]): void { + for (const splice of splices) { + if (splice.start <= this.currentIndex) { + if (this.currentIndex < splice.start + splice.deleteCount) { + this.currentIndex = -1; + this.next(); + } else { + this.currentIndex = rot(this.currentIndex + splice.inserted.length - splice.deleteCount - 1, this.model.changes.length); + this.next(); + } + } + } + } + + private onEditorMouseDown(e: IEditorMouseEvent): void { + this.mouseDownInfo = null; + + // if (!this.model) { + // return; + // } + + // if (this.model.changes.length === 0) { + // return; + // } + + const range = e.target.range; + + if (!range) { + return; + } + + if (!e.event.leftButton) { + return; + } + + if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) { + return; + } + + this.mouseDownInfo = { lineNumber: range.startLineNumber }; + } + + private onEditorMouseUp(e: IEditorMouseEvent): void { + if (!this.mouseDownInfo) { + return; + } + + const { lineNumber } = this.mouseDownInfo; + this.mouseDownInfo = null; + + const range = e.target.range; + + if (!range || range.startLineNumber !== lineNumber) { + return; + } + + if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) { + return; + } + + // const closestChangeIndex = this.findNextClosestChange(lineNumber); + // this.currentIndex = rot(closestChangeIndex - 1, this.model.changes.length); + this.next(lineNumber); + } + + private findNextClosestChange(lineNumber: number): number { + for (let i = 0; i < this.model.changes.length; i++) { + const change = this.model.changes[i]; + + if (getModifiedEndLineNumber(change) >= lineNumber) { + return i; + } + } + + return 0; + } + + private findPreviousClosestChange(lineNumber: number): number { + for (let i = this.model.changes.length - 1; i >= 0; i--) { + const change = this.model.changes[i]; + + if (change.modifiedStartLineNumber <= lineNumber) { + return i; + } + } + + return 0; + } + + dispose(): void { + return; + } +} export const editorGutterModifiedBackground = registerColor('editorGutter.modifiedBackground', { - dark: Color.fromHex('#00bcf2').transparent(0.6), - light: Color.fromHex('#007acc').transparent(0.6), - hc: Color.fromHex('#007acc').transparent(0.6) + dark: new Color(new RGBA(12, 125, 157)), + light: new Color(new RGBA(102, 175, 224)), + hc: new Color(new RGBA(0, 73, 122)) }, localize('editorGutterModifiedBackground', "Editor gutter background color for lines that are modified.")); export const editorGutterAddedBackground = registerColor('editorGutter.addedBackground', { - dark: Color.fromHex('#7fba00').transparent(0.6), - light: Color.fromHex('#2d883e').transparent(0.6), - hc: Color.fromHex('#2d883e').transparent(0.6) + dark: new Color(new RGBA(88, 124, 12)), + light: new Color(new RGBA(129, 184, 139)), + hc: new Color(new RGBA(27, 82, 37)) }, localize('editorGutterAddedBackground', "Editor gutter background color for lines that are added.")); export const editorGutterDeletedBackground = registerColor('editorGutter.deletedBackground', { - dark: Color.fromHex('#b9131a').transparent(0.76), - light: Color.fromHex('#b9131a').transparent(0.76), - hc: Color.fromHex('#b9131a').transparent(0.76) + dark: new Color(new RGBA(148, 21, 27)), + light: new Color(new RGBA(202, 75, 81)), + hc: new Color(new RGBA(141, 14, 20)) }, localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); - const overviewRulerDefault = new Color(new RGBA(0, 122, 204, 0.6)); export const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.')); export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.')); - -class DirtyDiffModelDecorator { +class DirtyDiffDecorator { static MODIFIED_DECORATION_OPTIONS = ModelDecorationOptions.register({ - linesDecorationsClassName: 'dirty-diff-modified-glyph', + linesDecorationsClassName: 'dirty-diff-glyph dirty-diff-modified', isWholeLine: true, overviewRuler: { color: themeColorFromId(overviewRulerModifiedForeground), darkColor: themeColorFromId(overviewRulerModifiedForeground), - position: common.OverviewRulerLane.Left + position: OverviewRulerLane.Left } }); static ADDED_DECORATION_OPTIONS = ModelDecorationOptions.register({ - linesDecorationsClassName: 'dirty-diff-added-glyph', + linesDecorationsClassName: 'dirty-diff-glyph dirty-diff-added', isWholeLine: true, overviewRuler: { color: themeColorFromId(overviewRulerAddedForeground), darkColor: themeColorFromId(overviewRulerAddedForeground), - position: common.OverviewRulerLane.Left + position: OverviewRulerLane.Left } }); static DELETED_DECORATION_OPTIONS = ModelDecorationOptions.register({ - linesDecorationsClassName: 'dirty-diff-deleted-glyph', + linesDecorationsClassName: 'dirty-diff-glyph dirty-diff-deleted', isWholeLine: true, overviewRuler: { color: themeColorFromId(overviewRulerDeletedForeground), darkColor: themeColorFromId(overviewRulerDeletedForeground), - position: common.OverviewRulerLane.Left + position: OverviewRulerLane.Left } }); private decorations: string[] = []; - private baselineModel: common.IModel; - private diffDelayer: ThrottledDelayer; - private _originalURIPromise: TPromise; - - private repositoryDisposables = new Set(); - private toDispose: IDisposable[] = []; + private disposables: IDisposable[] = []; constructor( - private model: common.IModel, - // private editor: CodeEditor, + private editorModel: IModel, + private model: DirtyDiffModel + ) { + model.onDidChange(this.onDidChange, this, this.disposables); + } + + private onDidChange(): void { + const decorations = this.model.changes.map((change) => { + const changeType = getChangeType(change); + const startLineNumber = change.modifiedStartLineNumber; + const endLineNumber = change.modifiedEndLineNumber || startLineNumber; + + switch (changeType) { + case ChangeType.Add: + return { + range: { + startLineNumber: startLineNumber, startColumn: 1, + endLineNumber: endLineNumber, endColumn: 1 + }, + options: DirtyDiffDecorator.ADDED_DECORATION_OPTIONS + }; + case ChangeType.Delete: + return { + range: { + startLineNumber: startLineNumber, startColumn: 1, + endLineNumber: startLineNumber, endColumn: 1 + }, + options: DirtyDiffDecorator.DELETED_DECORATION_OPTIONS + }; + case ChangeType.Modify: + return { + range: { + startLineNumber: startLineNumber, startColumn: 1, + endLineNumber: endLineNumber, endColumn: 1 + }, + options: DirtyDiffDecorator.MODIFIED_DECORATION_OPTIONS + }; + } + }); + + this.decorations = this.editorModel.deltaDecorations(this.decorations, decorations); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + + if (this.editorModel && !this.editorModel.isDisposed()) { + this.editorModel.deltaDecorations(this.decorations, []); + } + + this.editorModel = null; + this.decorations = []; + } +} + +export class DirtyDiffModel { + + private _originalModel: IModel; + get original(): IModel { return this._originalModel; } + get modified(): IModel { return this._editorModel; } + + private diffDelayer: ThrottledDelayer; + private _originalURIPromise: TPromise; + private repositoryDisposables = new Set(); + private disposables: IDisposable[] = []; + + private _onDidChange = new Emitter[]>(); + readonly onDidChange: Event[]> = this._onDidChange.event; + + private _changes: IChange[] = []; + get changes(): IChange[] { + return this._changes; + } + + constructor( + private _editorModel: IModel, @ISCMService private scmService: ISCMService, @IModelService private modelService: IModelService, @IEditorWorkerService private editorWorkerService: IEditorWorkerService, @@ -106,10 +750,10 @@ class DirtyDiffModelDecorator { @IWorkspaceContextService private contextService: IWorkspaceContextService, @ITextModelService private textModelResolverService: ITextModelService ) { - this.diffDelayer = new ThrottledDelayer(200); + this.diffDelayer = new ThrottledDelayer(200); - this.toDispose.push(model.onDidChangeContent(() => this.triggerDiff())); - scmService.onDidAddRepository(this.onDidAddRepository, this, this.toDispose); + this.disposables.push(_editorModel.onDidChangeContent(() => this.triggerDiff())); + scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); scmService.repositories.forEach(r => this.onDidAddRepository(r)); this.triggerDiff(); @@ -137,30 +781,35 @@ class DirtyDiffModelDecorator { return this.diffDelayer .trigger(() => this.diff()) - .then((diff: common.IChange[]) => { - if (!this.model || this.model.isDisposed() || !this.baselineModel || this.baselineModel.isDisposed()) { + .then((changes: IChange[]) => { + if (!this._editorModel || this._editorModel.isDisposed() || !this._originalModel || this._originalModel.isDisposed()) { return undefined; // disposed } - if (this.baselineModel.getValueLength() === 0) { - diff = []; + if (this._originalModel.getValueLength() === 0) { + changes = []; } - return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); + const diff = sortedDiff(this._changes, changes, (a, b) => b.modifiedStartLineNumber - a.modifiedStartLineNumber); + this._changes = changes; + + if (diff.length > 0) { + this._onDidChange.fire(diff); + } }); } - private diff(): TPromise { + private diff(): TPromise { return this.getOriginalURIPromise().then(originalURI => { - if (!this.model || this.model.isDisposed() || !originalURI) { + if (!this._editorModel || this._editorModel.isDisposed() || !originalURI) { return TPromise.as([]); // disposed } - if (!this.editorWorkerService.canComputeDirtyDiff(originalURI, this.model.uri)) { + if (!this.editorWorkerService.canComputeDirtyDiff(originalURI, this._editorModel.uri)) { return TPromise.as([]); // Files too large } - return this.editorWorkerService.computeDirtyDiff(originalURI, this.model.uri, true); + return this.editorWorkerService.computeDirtyDiff(originalURI, this._editorModel.uri, true); }); } @@ -172,15 +821,16 @@ class DirtyDiffModelDecorator { this._originalURIPromise = this.getOriginalResource() .then(originalUri => { if (!originalUri) { + this._originalModel = null; return null; } return this.textModelResolverService.createModelReference(originalUri) .then(ref => { - this.baselineModel = ref.object.textEditorModel; + this._originalModel = ref.object.textEditorModel; - this.toDispose.push(ref); - this.toDispose.push(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); + this.disposables.push(ref); + this.disposables.push(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); return originalUri; }); @@ -193,7 +843,7 @@ class DirtyDiffModelDecorator { private async getOriginalResource(): TPromise { for (const repository of this.scmService.repositories) { - const result = repository.provider.getOriginalResource(this.model.uri); + const result = repository.provider.getOriginalResource(this._editorModel.uri); if (result) { return result; @@ -203,54 +853,11 @@ class DirtyDiffModelDecorator { return null; } - private static changesToDecorations(diff: common.IChange[]): common.IModelDeltaDecoration[] { - return diff.map((change) => { - const startLineNumber = change.modifiedStartLineNumber; - const endLineNumber = change.modifiedEndLineNumber || startLineNumber; - - // Added - if (change.originalEndLineNumber === 0) { - return { - range: { - startLineNumber: startLineNumber, startColumn: 1, - endLineNumber: endLineNumber, endColumn: 1 - }, - options: DirtyDiffModelDecorator.ADDED_DECORATION_OPTIONS - }; - } - - // Removed - if (change.modifiedEndLineNumber === 0) { - return { - range: { - startLineNumber: startLineNumber, startColumn: 1, - endLineNumber: startLineNumber, endColumn: 1 - }, - options: DirtyDiffModelDecorator.DELETED_DECORATION_OPTIONS - }; - } - - // Modified - return { - range: { - startLineNumber: startLineNumber, startColumn: 1, - endLineNumber: endLineNumber, endColumn: 1 - }, - options: DirtyDiffModelDecorator.MODIFIED_DECORATION_OPTIONS - }; - }); - } - dispose(): void { - this.toDispose = dispose(this.toDispose); + this.disposables = dispose(this.disposables); - if (this.model && !this.model.isDisposed()) { - this.model.deltaDecorations(this.decorations, []); - } - - this.model = null; - this.baselineModel = null; - this.decorations = null; + this._editorModel = null; + this._originalModel = null; if (this.diffDelayer) { this.diffDelayer.cancel(); @@ -262,11 +869,21 @@ class DirtyDiffModelDecorator { } } -export class DirtyDiffDecorator implements ext.IWorkbenchContribution { +class DirtyDiffItem { - private models: common.IModel[] = []; - private decorators: { [modelId: string]: DirtyDiffModelDecorator } = Object.create(null); - private toDispose: IDisposable[] = []; + constructor(readonly model: DirtyDiffModel, readonly decorator: DirtyDiffDecorator) { } + + dispose(): void { + this.decorator.dispose(); + this.model.dispose(); + } +} + +export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, IModelRegistry { + + private models: IModel[] = []; + private items: { [modelId: string]: DirtyDiffItem; } = Object.create(null); + private disposables: IDisposable[] = []; constructor( @IMessageService private messageService: IMessageService, @@ -275,7 +892,7 @@ export class DirtyDiffDecorator implements ext.IWorkbenchContribution { @IWorkspaceContextService private contextService: IWorkspaceContextService, @IInstantiationService private instantiationService: IInstantiationService ) { - this.toDispose.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + this.disposables.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); } getId(): string { @@ -295,57 +912,91 @@ export class DirtyDiffDecorator implements ext.IWorkbenchContribution { // only interested in code editor widgets .filter(c => c instanceof CodeEditor) - // map to models - .map(editor => (editor as CodeEditor).getModel()) + // set model registry and map to models + .map(editor => { + const codeEditor = editor as CodeEditor; + const controller = DirtyDiffController.get(codeEditor); + controller.modelRegistry = this; + return codeEditor.getModel(); + }) // remove nulls and duplicates .filter((m, i, a) => !!m && !!m.uri && a.indexOf(m, i + 1) === -1); - const newModels = models.filter(p => this.models.every(m => p !== m)); - const oldModels = this.models.filter(m => models.every(p => p !== m)); + const newModels = models.filter(o => this.models.every(m => o !== m)); + const oldModels = this.models.filter(m => models.every(o => o !== m)); - newModels.forEach(m => this.onModelVisible(m)); oldModels.forEach(m => this.onModelInvisible(m)); + newModels.forEach(m => this.onModelVisible(m)); this.models = models; } - private onModelVisible(model: common.IModel): void { - this.decorators[model.id] = this.instantiationService.createInstance(DirtyDiffModelDecorator, model); + private onModelVisible(editorModel: IModel): void { + const model = this.instantiationService.createInstance(DirtyDiffModel, editorModel); + const decorator = new DirtyDiffDecorator(editorModel, model); + + this.items[editorModel.id] = new DirtyDiffItem(model, decorator); } - private onModelInvisible(model: common.IModel): void { - this.decorators[model.id].dispose(); - delete this.decorators[model.id]; + private onModelInvisible(editorModel: IModel): void { + this.items[editorModel.id].dispose(); + delete this.items[editorModel.id]; + } + + getModel(editorModel: IModel): DirtyDiffModel | null { + const item = this.items[editorModel.id]; + + if (!item) { + return null; + } + + return item.model; } dispose(): void { - this.toDispose = dispose(this.toDispose); - this.models.forEach(m => this.decorators[m.id].dispose()); + this.disposables = dispose(this.disposables); + this.models.forEach(m => this.items[m.id].dispose()); + this.models = null; - this.decorators = null; + this.items = null; } } registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const editorGutterModifiedBackgroundColor = theme.getColor(editorGutterModifiedBackground); if (editorGutterModifiedBackgroundColor) { - collector.addRule(`.monaco-editor .dirty-diff-modified-glyph { border-left: 3px solid ${editorGutterModifiedBackgroundColor}; }`); + collector.addRule(` + .monaco-editor .dirty-diff-modified { + border-left: 3px solid ${editorGutterModifiedBackgroundColor}; + } + .monaco-editor .dirty-diff-modified:before { + background: ${editorGutterModifiedBackgroundColor}; + } + `); } const editorGutterAddedBackgroundColor = theme.getColor(editorGutterAddedBackground); if (editorGutterAddedBackgroundColor) { - collector.addRule(`.monaco-editor .dirty-diff-added-glyph { border-left: 3px solid ${editorGutterAddedBackgroundColor}; }`); + collector.addRule(` + .monaco-editor .dirty-diff-added { + border-left: 3px solid ${editorGutterAddedBackgroundColor}; + } + .monaco-editor .dirty-diff-added:before { + background: ${editorGutterAddedBackgroundColor}; + } + `); } const editorGutteDeletedBackgroundColor = theme.getColor(editorGutterDeletedBackground); if (editorGutteDeletedBackgroundColor) { collector.addRule(` - .monaco-editor .dirty-diff-deleted-glyph:after { - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; + .monaco-editor .dirty-diff-deleted:after { border-left: 4px solid ${editorGutteDeletedBackgroundColor}; } + .monaco-editor .dirty-diff-deleted:before { + background: ${editorGutteDeletedBackgroundColor}; + } `); } }); diff --git a/src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css b/src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css index beaf772629e..e55cb63c612 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .dirty-diff-modified-glyph, -.monaco-editor .dirty-diff-added-glyph, -.monaco-editor .dirty-diff-deleted-glyph:after { +.monaco-editor .dirty-diff-glyph { margin-left: 5px; + cursor: pointer; } -.monaco-editor .dirty-diff-deleted-glyph:after { +.monaco-editor .dirty-diff-deleted:after { content: ''; position: absolute; bottom: -4px; @@ -17,4 +16,37 @@ width: 4px; height: 0; z-index: 9; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + transition: border-top-width 80ms linear, border-bottom-width 80ms linear, bottom 80ms linear; +} + +.monaco-editor .dirty-diff-glyph:before { + position: absolute; + content: ''; + height: 100%; + width: 0; + left: -2px; + transition: width 80ms linear, left 80ms linear; +} + +.monaco-editor .dirty-diff-deleted:before { + margin-left: 3px; + height: 0; + bottom: 0; + transition: height 80ms linear; +} + +.monaco-editor .margin-view-overlays > div:hover > .dirty-diff-glyph:before { + position: absolute; + content: ''; + height: 100%; + width: 9px; + left: -6px; +} + +.monaco-editor .margin-view-overlays > div:hover > .dirty-diff-deleted:after { + bottom: 0; + border-top-width: 0; + border-bottom-width: 0; } \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index b5ee2acfc51..b5465c83f69 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { DirtyDiffDecorator } from './dirtydiffDecorator'; +import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ToggleViewletAction } from 'vs/workbench/browser/viewlet'; import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; @@ -30,7 +30,7 @@ class OpenSCMViewletAction extends ToggleViewletAction { } Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(DirtyDiffDecorator); + .registerWorkbenchContribution(DirtyDiffWorkbenchController); const viewletDescriptor = new ViewletDescriptor( SCMViewlet, diff --git a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts index 1814995bbef..7e9faa28fd4 100644 --- a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts +++ b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts @@ -11,8 +11,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); import { ThrottledDelayer } from 'vs/base/common/async'; import types = require('vs/base/common/types'); -import { isWindows } from 'vs/base/common/platform'; -import strings = require('vs/base/common/strings'); import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenModel, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; @@ -25,7 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchSearchConfiguration } from 'vs/workbench/parts/search/common/search'; import { IRange } from 'vs/editor/common/core/range'; -import { compareItemsByScore, scoreItem, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; export import OpenSymbolHandler = openSymbolHandler.OpenSymbolHandler; // OpenSymbolHandler is used from an extension and must be in the main bundle file so it can load @@ -150,7 +148,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { } private registerListeners(): void { - this.configurationService.onDidUpdateConfiguration(e => this.updateHandlers(this.configurationService.getConfiguration())); + this.configurationService.onDidChangeConfiguration(e => this.updateHandlers(this.configurationService.getConfiguration())); } private updateHandlers(configuration: IWorkbenchSearchConfiguration): void { @@ -175,18 +173,16 @@ export class OpenAnythingHandler extends QuickOpenHandler { this.cancelPendingSearch(); this.isClosed = false; // Treat this call as the handler being in use - // Massage search value - searchValue = searchValue.replace(/ /g, ''); // get rid of all whitespace - if (isWindows) { - searchValue = searchValue.replace(/\//g, '\\'); // Help Windows users to search for paths when using slash - } + // Prepare search for scoring + const query = prepareQuery(searchValue); - const searchWithRange = this.extractRange(searchValue); // Find a suitable range from the pattern looking for ":" and "#" + const searchWithRange = this.extractRange(query.value); // Find a suitable range from the pattern looking for ":" and "#" if (searchWithRange) { - searchValue = searchWithRange.search; // ignore range portion in query + query.value = searchWithRange.search; // ignore range portion in query + query.lowercase = query.value.toLowerCase(); } - if (!searchValue) { + if (!query.value) { return TPromise.as(new QuickOpenModel()); // Respond directly to empty search } @@ -195,12 +191,12 @@ export class OpenAnythingHandler extends QuickOpenHandler { const resultPromises: TPromise[] = []; // File Results - const filePromise = this.openFileHandler.getResults(searchValue, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); + const filePromise = this.openFileHandler.getResults(query.value, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); resultPromises.push(filePromise); // Symbol Results (unless disabled or a range or absolute path is specified) if (this.includeSymbols && !searchWithRange) { - resultPromises.push(this.openSymbolHandler.getResults(searchValue)); + resultPromises.push(this.openSymbolHandler.getResults(query.value)); } // Join and sort unified @@ -217,8 +213,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { // Sort const unsortedResultTime = Date.now(); - const normalizedSearchValue = strings.stripWildcards(searchValue); - const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => compareItemsByScore(elementA, elementB, normalizedSearchValue, true, QuickOpenItemAccessor, this.scorerCache); + const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => compareItemsByScore(elementA, elementB, query, true, QuickOpenItemAccessor, this.scorerCache); const viewResults = arrays.top(mergedResults, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); const sortedResultTime = Date.now(); @@ -227,7 +222,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { if (entry instanceof FileEntry) { entry.setRange(searchWithRange ? searchWithRange.range : null); - const itemScore = scoreItem(entry, normalizedSearchValue, true, QuickOpenItemAccessor, this.scorerCache); + const itemScore = scoreItem(entry, query, true, QuickOpenItemAccessor, this.scorerCache); entry.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch); } }); @@ -235,7 +230,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { const duration = new Date().getTime() - startTime; filePromise.then(fileModel => { const data = this.createTimerEventData(startTime, { - searchLength: searchValue.length, + searchLength: query.value.length, unsortedResultTime, sortedResultTime, resultCount: mergedResults.length, diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 8c65a7a44b1..e50b636779e 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -507,7 +507,7 @@ export abstract class AbstractSearchAndReplaceAction extends Action { export class RemoveAction extends AbstractSearchAndReplaceAction { constructor(private viewer: ITree, private element: RenderableMatch) { - super('remove', nls.localize('RemoveAction.label', "Remove"), 'action-remove'); + super('remove', nls.localize('RemoveAction.label', "Dismiss"), 'action-remove'); } public run(): TPromise { diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index 9b3120dcdc6..cf614eeaeb0 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -56,7 +56,7 @@ export class QueryBuilder { } const useRipgrep = !folderResources || folderResources.every(folder => { - const folderConfig = this.configurationService.getConfiguration(undefined, { resource: folder }); + const folderConfig = this.configurationService.getConfiguration({ resource: folder }); return folderConfig.search.useRipgrep; }); @@ -244,7 +244,7 @@ export class QueryBuilder { private getFolderQueryForSearchPath(searchPath: ISearchPathPattern): IFolderQuery { const folder = searchPath.searchPath; - const folderConfig = this.configurationService.getConfiguration(undefined, { resource: folder }); + const folderConfig = this.configurationService.getConfiguration({ resource: folder }); return { folder, includePattern: searchPath.pattern && patternListToIExpression([searchPath.pattern]), @@ -253,7 +253,7 @@ export class QueryBuilder { } private getFolderQueryForRoot(folder: uri, options?: IQueryOptions): IFolderQuery { - const folderConfig = this.configurationService.getConfiguration(undefined, { resource: folder }); + const folderConfig = this.configurationService.getConfiguration({ resource: folder }); return { folder, excludePattern: this.getExcludesForFolder(folderConfig, options), diff --git a/src/vs/workbench/parts/tasks/common/taskTemplates.ts b/src/vs/workbench/parts/tasks/common/taskTemplates.ts index 6165c13bc0a..35a33f94ca9 100644 --- a/src/vs/workbench/parts/tasks/common/taskTemplates.ts +++ b/src/vs/workbench/parts/tasks/common/taskTemplates.ts @@ -54,7 +54,7 @@ const msbuild: TaskEntry = { '\t"tasks": [', '\t\t{', '\t\t\t"taskName": "build",', - '\t\t\t"type": "process",', + '\t\t\t"type": "shell",', '\t\t\t"command": "msbuild",', '\t\t\t"args": [', '\t\t\t\t// Ask msbuild to generate full paths for file names.', diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 1737491b685..14c8cddf519 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -35,7 +35,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IMessageService, IChoiceService } from 'vs/platform/message/common/message'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -63,7 +63,6 @@ import Constants from 'vs/workbench/parts/markers/common/constants'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IConfigurationEditingService, ConfigurationTarget, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -551,7 +550,6 @@ class TaskService extends EventEmitter implements ITaskService { private modeService: IModeService; private configurationService: IConfigurationService; - private configurationEditingService: IConfigurationEditingService; private markerService: IMarkerService; private outputService: IOutputService; private messageService: IMessageService; @@ -582,7 +580,6 @@ class TaskService extends EventEmitter implements ITaskService { private _outputChannel: IOutputChannel; constructor( @IModeService modeService: IModeService, @IConfigurationService configurationService: IConfigurationService, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService, @IMarkerService markerService: IMarkerService, @IOutputService outputService: IOutputService, @IMessageService messageService: IMessageService, @IChoiceService choiceService: IChoiceService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @@ -604,7 +601,6 @@ class TaskService extends EventEmitter implements ITaskService { super(); this.modeService = modeService; this.configurationService = configurationService; - this.configurationEditingService = configurationEditingService; this.markerService = markerService; this.outputService = outputService; this.messageService = messageService; @@ -624,7 +620,7 @@ class TaskService extends EventEmitter implements ITaskService { this._taskSystemListeners = []; this._outputChannel = this.outputService.getChannel(TaskService.OutputChannelId); this._providers = new Map(); - this.configurationService.onDidUpdateConfiguration(() => { + this.configurationService.onDidChangeConfiguration(() => { if (!this._taskSystem && !this._workspaceTasksPromise) { return; } @@ -1108,32 +1104,25 @@ class TaskService extends EventEmitter implements ITaskService { } promise = this.fileService.createFile(workspaceFolder.toResource('.vscode/tasks.json'), content).then(() => { }); } else { - let value: IConfigurationValue = { key: undefined, value: undefined }; // We have a global task configuration if (index === -1) { if (properties.problemMatcher !== void 0) { fileConfig.problemMatcher = properties.problemMatcher; - value.key = 'tasks.problemMatchers'; - value.value = fileConfig.problemMatcher; - promise = this.writeConfiguration(workspaceFolder, value); + promise = this.writeConfiguration(workspaceFolder, 'tasks.problemMatchers', fileConfig.problemMatcher); } else if (properties.group !== void 0) { fileConfig.group = properties.group; - value.key = 'tasks.group'; - value.value = fileConfig.group; - promise = this.writeConfiguration(workspaceFolder, value); + promise = this.writeConfiguration(workspaceFolder, 'tasks.group', fileConfig.group); } } else { if (!Array.isArray(fileConfig.tasks)) { fileConfig.tasks = []; } - value.key = 'tasks.tasks'; - value.value = fileConfig.tasks; if (index === void 0) { fileConfig.tasks.push(toCustomize); } else { fileConfig.tasks[index] = toCustomize; } - promise = this.writeConfiguration(workspaceFolder, value); + promise = this.writeConfiguration(workspaceFolder, 'tasks.tasks', fileConfig.tasks); } }; if (!promise) { @@ -1162,11 +1151,11 @@ class TaskService extends EventEmitter implements ITaskService { }); } - private writeConfiguration(workspaceFolder: IWorkspaceFolder, value: IConfigurationValue): TPromise { + private writeConfiguration(workspaceFolder: IWorkspaceFolder, key: string, value: any): TPromise { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { - return this.configurationEditingService.writeConfiguration(ConfigurationTarget.WORKSPACE, value); + return this.configurationService.updateValue(key, value, { resource: workspaceFolder.uri }, ConfigurationTarget.WORKSPACE); } else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - return this.configurationEditingService.writeConfiguration(ConfigurationTarget.FOLDER, value, { scopes: { resource: workspaceFolder.uri } }); + return this.configurationService.updateValue(key, value, { resource: workspaceFolder.uri }, ConfigurationTarget.WORKSPACE_FOLDER); } else { return undefined; } @@ -1421,6 +1410,8 @@ class TaskService extends EventEmitter implements ITaskService { let legacyTaskConfigurations = folderTasks.set ? this.getLegacyTaskConfigurations(folderTasks.set) : undefined; let customTasksToDelete: Task[] = []; if (configurations || legacyTaskConfigurations) { + let unUsedConfigurations: Set = new Set(); + Object.keys(configurations.byIdentifier).forEach(key => unUsedConfigurations.add(key)); for (let task of contributed) { if (!ContributedTask.is(task)) { continue; @@ -1428,6 +1419,7 @@ class TaskService extends EventEmitter implements ITaskService { if (configurations) { let configuringTask = configurations.byIdentifier[task.defines._key]; if (configuringTask) { + unUsedConfigurations.delete(task.defines._key); result.add(key, TaskConfig.createCustomTask(task, configuringTask)); } else { result.add(key, task); @@ -1458,6 +1450,16 @@ class TaskService extends EventEmitter implements ITaskService { } else { result.add(key, ...folderTasks.set.tasks); } + unUsedConfigurations.forEach((value) => { + let configuringTask = configurations.byIdentifier[value]; + this._outputChannel.append(nls.localize( + 'TaskService.noConfiguration', + 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n', + configuringTask.configures.type, + JSON.stringify(configuringTask._source.config.element, undefined, 4) + )); + this.showOutput(); + }); } else { result.add(key, ...folderTasks.set.tasks); result.add(key, ...contributed); diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 446001d7d91..b3e4d73c599 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -397,6 +397,9 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { }); }); } + if (!terminal) { + return TPromise.wrapError(new Error(`Failed to create terminal for task ${task._label}`)); + } this.terminalService.setActiveInstance(terminal); if (task.command.presentation.reveal === RevealKind.Always || (task.command.presentation.reveal === RevealKind.Silent && task.problemMatchers.length === 0)) { this.terminalService.showPanel(task.command.presentation.focus); diff --git a/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts b/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts index 0f5e860dc61..dc1412e510e 100644 --- a/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts +++ b/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts @@ -14,6 +14,8 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { stripWildcards } from 'vs/base/common/strings'; import { matchesFuzzy } from 'vs/base/common/filters'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { PICK_WORKSPACE_FOLDER_COMMAND } from 'vs/workbench/browser/actions/workspaceActions'; export class TerminalEntry extends QuickOpenEntry { @@ -49,7 +51,8 @@ export class CreateTerminal extends QuickOpenEntry { constructor( private label: string, - private terminalService: ITerminalService + private terminalService: ITerminalService, + private commandService: ICommandService ) { super(); } @@ -65,9 +68,14 @@ export class CreateTerminal extends QuickOpenEntry { public run(mode: Mode, context: IEntryRunContext): boolean { if (mode === Mode.OPEN) { setTimeout(() => { - const newTerminal = this.terminalService.createInstance(); - this.terminalService.setActiveInstance(newTerminal); - this.terminalService.showPanel(true); + return this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND).then(workspace => { + const instance = this.terminalService.createInstance({ cwd: workspace.uri.fsPath }, true); + if (!instance) { + return TPromise.as(void 0); + } + this.terminalService.setActiveInstance(instance); + return this.terminalService.showPanel(true); + }); }, 0); return true; } @@ -82,6 +90,7 @@ export class TerminalPickerHandler extends QuickOpenHandler { constructor( @ITerminalService private terminalService: ITerminalService, + @ICommandService private commandService: ICommandService, @IPanelService private panelService: IPanelService ) { super(); @@ -92,7 +101,7 @@ export class TerminalPickerHandler extends QuickOpenHandler { const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase(); const terminalEntries: QuickOpenEntry[] = this.getTerminals(); - terminalEntries.push(new CreateTerminal(nls.localize("'workbench.action.terminal.newplus", "$(plus) Create New Integrated Terminal"), this.terminalService)); + terminalEntries.push(new CreateTerminal(nls.localize("'workbench.action.terminal.newplus", "$(plus) Create New Integrated Terminal"), this.terminalService, this.commandService)); const entries = terminalEntries.filter(e => { if (!searchValue) { diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 93ffb4af4c6..fe4841ac5ec 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -58,7 +58,7 @@ export interface ITerminalConfiguration { osx: string[]; windows: string[]; }; - // enableBold: boolean; + enableBold: boolean; rightClickCopyPaste: boolean; cursorBlinking: boolean; cursorStyle: string; diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 305ac98c424..44815065a04 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -43,7 +43,7 @@ export abstract class TerminalService implements ITerminalService { constructor( @IContextKeyService private _contextKeyService: IContextKeyService, - @IConfigurationService private _configurationService: IConfigurationService, + @IConfigurationService protected _configurationService: IConfigurationService, @IPanelService protected _panelService: IPanelService, @IPartService private _partService: IPartService, @ILifecycleService lifecycleService: ILifecycleService @@ -59,7 +59,7 @@ export abstract class TerminalService implements ITerminalService { this._onInstanceTitleChanged = new Emitter(); this._onInstancesChanged = new Emitter(); - this._configurationService.onDidUpdateConfiguration(() => this.updateConfig()); + this._configurationService.onDidChangeConfiguration(() => this.updateConfig()); lifecycleService.onWillShutdown(event => event.veto(this._onWillShutdown())); this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService); this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index f5d947c4492..184802fb0da 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -18,7 +18,7 @@ import { TERMINAL_DEFAULT_SHELL_LINUX, TERMINAL_DEFAULT_SHELL_OSX, TERMINAL_DEFA import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, CreateNewSelectWorkspaceTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -140,11 +140,11 @@ configurationRegistry.registerConfiguration({ 'type': 'number', 'default': 1 }, - // 'terminal.integrated.enableBold': { - // 'type': 'boolean', - // 'description': nls.localize('terminal.integrated.enableBold', "Whether to enable bold text within the terminal, this requires support from the terminal shell."), - // 'default': true - // }, + 'terminal.integrated.enableBold': { + 'type': 'boolean', + 'description': nls.localize('terminal.integrated.enableBold', "Whether to enable bold text within the terminal, note that this requires support from the terminal shell."), + 'default': true + }, 'terminal.integrated.cursorBlinking': { 'description': nls.localize('terminal.integrated.cursorBlinking', "Controls whether the terminal cursor blinks."), 'type': 'boolean', @@ -187,6 +187,7 @@ configurationRegistry.registerConfiguration({ QUICKOPEN_ACTION_ID, ShowAllCommandsAction.ID, CreateNewTerminalAction.ID, + CreateNewSelectWorkspaceTerminalAction.ID, CopyTerminalSelectionAction.ID, KillTerminalAction.ID, FocusActiveTerminalAction.ID, @@ -279,6 +280,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTermina primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK } }), 'Terminal: Create New Integrated Terminal', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewSelectWorkspaceTerminalAction, CreateNewSelectWorkspaceTerminalAction.ID, CreateNewSelectWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (Select Workspace)', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index b38714c7e04..13ad6995359 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -21,6 +21,8 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { ActionBarContributor } from 'vs/workbench/browser/actions'; import { TerminalEntry } from 'vs/workbench/parts/terminal/browser/terminalQuickOpen'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { PICK_WORKSPACE_FOLDER_COMMAND } from 'vs/workbench/browser/actions/workspaceActions'; export const TERMINAL_PICKER_PREFIX = 'term '; @@ -196,14 +198,12 @@ export class CreateNewTerminalAction extends Action { public static ID = 'workbench.action.terminal.new'; public static LABEL = nls.localize('workbench.action.terminal.new', "Create New Integrated Terminal"); - public static PANEL_LABEL = nls.localize('workbench.action.terminal.new.short', "New Terminal"); constructor( id: string, label: string, @ITerminalService private terminalService: ITerminalService ) { super(id, label); - this.class = 'terminal-action new'; } public run(event?: any): TPromise { @@ -216,6 +216,33 @@ export class CreateNewTerminalAction extends Action { } } +export class CreateNewSelectWorkspaceTerminalAction extends Action { + + public static ID = 'workbench.action.terminal.newSelectWorkspace'; + public static LABEL = nls.localize('workbench.action.terminal.newSelectWorkspace', "Create New Integrated Terminal (Select Workspace)"); + public static PANEL_LABEL = nls.localize('workbench.action.terminal.new.short', "New Terminal"); + + constructor( + id: string, label: string, + @ITerminalService private terminalService: ITerminalService, + @ICommandService private commandService: ICommandService + ) { + super(id, label); + this.class = 'terminal-action new'; + } + + public run(event?: any): TPromise { + return this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND).then(workspace => { + const instance = this.terminalService.createInstance(workspace ? { cwd: workspace.uri.fsPath } : undefined, true); + if (!instance) { + return TPromise.as(void 0); + } + this.terminalService.setActiveInstance(instance); + return this.terminalService.showPanel(true); + }); + } +} + export class FocusActiveTerminalAction extends Action { public static ID = 'workbench.action.terminal.focus'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index b1793339026..753f9d5e055 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -123,8 +123,8 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig): void { // Check whether there is a workspace setting const platformKey = platform.isWindows ? 'windows' : platform.isMacintosh ? 'osx' : 'linux'; - const shellConfigValue = this._workspaceConfigurationService.lookup(`terminal.integrated.shell.${platformKey}`); - const shellArgsConfigValue = this._workspaceConfigurationService.lookup(`terminal.integrated.shellArgs.${platformKey}`); + const shellConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shell.${platformKey}`); + const shellArgsConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shellArgs.${platformKey}`); // Check if workspace setting exists and whether it's whitelisted let isWorkspaceShellAllowed = false; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 4ba392d7be8..378e96b3648 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -266,7 +266,8 @@ export class TerminalInstance implements ITerminalInstance { theme: this._getXtermTheme(), fontFamily: font.fontFamily, fontSize: font.fontSize, - lineHeight: font.lineHeight + lineHeight: font.lineHeight, + enableBold: this._configHelper.config.enableBold }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); @@ -511,6 +512,16 @@ export class TerminalInstance implements ITerminalInstance { // background since scrollTop changes take no effect but the terminal's position does // change since the number of visible rows decreases. this._xterm.emit('scroll', this._xterm.buffer.ydisp); + if (this._container) { + // Force a layout when the instance becomes invisible. This is particularly important + // for ensuring that terminals that are created in the background by an extension will + // correctly get correct character measurements in order to render to the screen (see + // #34554). + const computedStyle = window.getComputedStyle(this._container); + const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); + const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10); + this.layout(new Dimension(width, height)); + } } } @@ -902,21 +913,31 @@ export class TerminalInstance implements ITerminalInstance { if (!terminalWidth) { return; } + if (this._xterm) { const font = this._configHelper.getFont(); - if (this._xterm.getOption('lineHeight') !== font.lineHeight) { - this._xterm.setOption('lineHeight', font.lineHeight); - } - if (this._xterm.getOption('fontSize') !== font.fontSize) { - this._xterm.setOption('fontSize', font.fontSize); - } - if (this._xterm.getOption('fontFamily') !== font.fontFamily) { - this._xterm.setOption('fontFamily', font.fontFamily); + + // Only apply these settings when the terminal is visible so that + // the characters are measured correctly. + if (this._isVisible) { + if (this._xterm.getOption('lineHeight') !== font.lineHeight) { + this._xterm.setOption('lineHeight', font.lineHeight); + } + if (this._xterm.getOption('fontSize') !== font.fontSize) { + this._xterm.setOption('fontSize', font.fontSize); + } + if (this._xterm.getOption('fontFamily') !== font.fontFamily) { + this._xterm.setOption('fontFamily', font.fontFamily); + } + if (this._xterm.getOption('enableBold') !== this._configHelper.config.enableBold) { + this._xterm.setOption('enableBold', this._configHelper.config.enableBold); + } } this._xterm.resize(this._cols, this._rows); this._xterm.element.style.width = terminalWidth + 'px'; } + this._processReady.then(() => { if (this._process && this._process.connected) { // The child process could aready be terminated diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts index 83b76d604ac..8650646c7ad 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -14,6 +14,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TerminalWidgetManager } from 'vs/workbench/parts/terminal/browser/terminalWidgetManager'; import { TPromise } from 'vs/base/common/winjs.base'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; @@ -67,7 +68,8 @@ export class TerminalLinkHandler { private _initialCwd: string, @IOpenerService private _openerService: IOpenerService, @IWorkbenchEditorService private _editorService: IWorkbenchEditorService, - @IConfigurationService private _configurationService: IConfigurationService + @IConfigurationService private _configurationService: IConfigurationService, + @ITerminalService private _terminalService: ITerminalService ) { const baseLocalLinkClause = _platform === platform.Platform.Windows ? winLocalLinkClause : unixLocalLinkClause; // Append line and column number regex @@ -120,6 +122,9 @@ export class TerminalLinkHandler { event.preventDefault(); // Require correct modifier on click if (!this._isLinkActivationModifierDown(event)) { + // If the modifier is not pressed, the terminal should be + // focused if it's not already + this._terminalService.getActiveInstance().focus(true); return false; } return handler(uri); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 56a3523d7e2..d382d1f2aa5 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -19,7 +19,7 @@ import { ITerminalService, ITerminalFont, TERMINAL_PANEL_ID } from 'vs/workbench import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from './terminalFindWidget'; import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { KillTerminalAction, CreateNewTerminalAction, SwitchTerminalInstanceAction, SwitchTerminalInstanceActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, CreateNewSelectWorkspaceTerminalAction, SwitchTerminalInstanceAction, SwitchTerminalInstanceActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Panel } from 'vs/workbench/browser/panel'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -74,7 +74,7 @@ export class TerminalPanel extends Panel { this._terminalService.setContainers(this.getContainer().getHTMLElement(), this._terminalContainer); this._register(this.themeService.onThemeChange(theme => this._updateTheme(theme))); - this._register(this._configurationService.onDidUpdateConfiguration(() => this._updateFont())); + this._register(this._configurationService.onDidChangeConfiguration(() => this._updateFont())); this._updateFont(); this._updateTheme(); @@ -115,7 +115,7 @@ export class TerminalPanel extends Panel { if (!this._actions) { this._actions = [ this._instantiationService.createInstance(SwitchTerminalInstanceAction, SwitchTerminalInstanceAction.ID, SwitchTerminalInstanceAction.LABEL), - this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.PANEL_LABEL), + this._instantiationService.createInstance(CreateNewSelectWorkspaceTerminalAction, CreateNewSelectWorkspaceTerminalAction.ID, CreateNewSelectWorkspaceTerminalAction.PANEL_LABEL), this._instantiationService.createInstance(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.PANEL_LABEL) ]; this._actions.forEach(a => { @@ -129,7 +129,7 @@ export class TerminalPanel extends Panel { if (!this._contextMenuActions) { this._copyContextMenuAction = this._instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, nls.localize('copy', "Copy")); this._contextMenuActions = [ - this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, nls.localize('createNewTerminal', "New Terminal")), + this._instantiationService.createInstance(CreateNewSelectWorkspaceTerminalAction, CreateNewSelectWorkspaceTerminalAction.ID, CreateNewSelectWorkspaceTerminalAction.PANEL_LABEL), new Separator(), this._copyContextMenuAction, this._instantiationService.createInstance(TerminalPasteAction, TerminalPasteAction.ID, nls.localize('paste', "Paste")), @@ -302,8 +302,6 @@ export class TerminalPanel extends Panel { this._font = this._terminalService.configHelper.getFont(); // TODO: Can we support ligatures? // dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures); - // TODO: How to handle Disable bold? - // dom.toggleClass(this._parentDomElement, 'disable-bold', !this._terminalService.configHelper.config.enableBold); this.layout(new Dimension(this._parentDomElement.offsetWidth, this._parentDomElement.offsetHeight)); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 512c65ed2d4..66d5340d584 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -11,8 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IQuickOpenService, IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { ITerminalInstance, ITerminalService, IShellLaunchConfig, ITerminalConfigHelper, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, TERMINAL_PANEL_ID } from 'vs/workbench/parts/terminal/common/terminal'; import { TerminalService as AbstractTerminalService } from 'vs/workbench/parts/terminal/common/terminalService'; @@ -39,7 +38,6 @@ export class TerminalService extends AbstractTerminalService implements ITermina @IInstantiationService private _instantiationService: IInstantiationService, @IWindowService private _windowService: IWindowService, @IQuickOpenService private _quickOpenService: IQuickOpenService, - @IConfigurationEditingService private _configurationEditingService: IConfigurationEditingService, @IChoiceService private _choiceService: IChoiceService, @IStorageService private _storageService: IStorageService, @IMessageService private _messageService: IMessageService @@ -163,8 +161,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina return null; } const shell = value.description; - const configChange = { key: 'terminal.integrated.shell.windows', value: shell }; - return this._configurationEditingService.writeConfiguration(ConfigurationTarget.USER, configChange).then(() => shell); + return this._configurationService.updateValue('terminal.integrated.shell.windows', shell, ConfigurationTarget.USER).then(() => shell); }); }); } diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts index 3eecd534b8c..2fb9993bf0a 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts @@ -6,7 +6,7 @@ 'use strict'; import * as assert from 'assert'; -import { IConfigurationService, getConfigurationValue, IConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, getConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Platform } from 'vs/base/common/platform'; import { TPromise } from 'vs/base/common/winjs.base'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; @@ -17,13 +17,14 @@ class MockConfigurationService implements IConfigurationService { public _serviceBrand: any; public serviceId = IConfigurationService; public constructor(private configuration: any = {}) { } - public reloadConfiguration(section?: string): TPromise { return TPromise.as(this.getConfiguration()); } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: void 0, folder: void 0 }; } - public keys() { return { default: [], user: [], workspace: [], folder: [] }; } - public values() { return {}; } + public inspect(key: string, overrides?: IConfigurationOverrides): any { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: void 0, workspaceFolder: void 0 }; } + public keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; } public getConfiguration(): any { return this.configuration; } + public getValue(key: string, overrides?: IConfigurationOverrides): T { return getConfigurationValue(this.getConfiguration(), key); } + public updateValue(): TPromise { return null; } public getConfigurationData(): any { return null; } - public onDidUpdateConfiguration() { return { dispose() { } }; } + public onDidChangeConfiguration() { return { dispose() { } }; } + public reloadConfiguration() { return null; } } suite('Workbench - TerminalConfigHelper', () => { diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts index 77d9fed7145..491637a485c 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -35,7 +35,7 @@ interface LinkFormatInfo { suite('Workbench - TerminalLinkHandler', () => { suite('localLinkRegex', () => { test('Windows', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, null, null, null, null); + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, null, null, null, null, null); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -105,7 +105,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('Linux', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null, null, null, null); + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null, null, null, null, null); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -169,7 +169,7 @@ suite('Workbench - TerminalLinkHandler', () => { suite('preprocessPath', () => { test('Windows', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, 'C:\\base', null, null, null); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, 'C:\\base', null, null, null, null); let stub = sinon.stub(path, 'join', function (arg1, arg2) { return arg1 + '\\' + arg2; @@ -182,7 +182,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('Linux', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, '/base', null, null, null); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, '/base', null, null, null, null); let stub = sinon.stub(path, 'join', function (arg1, arg2) { return arg1 + '/' + arg2; @@ -195,7 +195,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('No Workspace', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null, null, null, null); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null, null, null, null, null); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index 2c44384ef13..d8f6bebd750 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -20,11 +20,11 @@ import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/co import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { Delayer } from 'vs/base/common/async'; -import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Color } from 'vs/base/common/color'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export class SelectColorThemeAction extends Action { @@ -60,7 +60,7 @@ export class SelectColorThemeAction extends Action { } let target = null; if (applyTheme) { - let confValue = this.configurationService.lookup(COLOR_THEME_SETTING); + let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } @@ -126,7 +126,7 @@ class SelectIconThemeAction extends Action { } let target = null; if (applyTheme) { - let confValue = this.configurationService.lookup(ICON_THEME_SETTING); + let confValue = this.configurationService.inspect(ICON_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } this.themeService.setFileIconTheme(theme && theme.id, target).done(null, diff --git a/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts b/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts index 21dbeb9472c..a008c9c954f 100644 --- a/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts +++ b/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts @@ -35,7 +35,7 @@ class UnsupportedWorkspaceSettingsContribution implements IWorkbenchContribution @IStorageService private storageService: IStorageService ) { lifecycleService.onShutdown(this.dispose, this); - this.toDispose.push(this.workspaceConfigurationService.onDidUpdateConfiguration(e => this.checkWorkspaceSettings())); + this.toDispose.push(this.workspaceConfigurationService.onDidChangeConfiguration(e => this.checkWorkspaceSettings())); } getId(): string { diff --git a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts index 5420ab82b04..8acbac70b41 100644 --- a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts +++ b/src/vs/workbench/parts/watermark/electron-browser/watermark.ts @@ -100,6 +100,7 @@ const folderEntries = [ ]; const UNBOUND = nls.localize('watermark.unboundCommand', "unbound"); +const WORKBENCH_TIPS_ENABLED_KEY = 'workbench.tips.enabled'; export class WatermarkContribution implements IWorkbenchContribution { @@ -120,19 +121,21 @@ export class WatermarkContribution implements IWorkbenchContribution { lifecycleService.onShutdown(this.dispose, this); this.partService.joinCreation().then(() => { - this.enabled = this.configurationService.lookup('workbench.tips.enabled').value; + this.enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); if (this.enabled) { this.create(); } }); - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => { - const enabled = this.configurationService.lookup('workbench.tips.enabled').value; - if (enabled !== this.enabled) { - this.enabled = enabled; - if (this.enabled) { - this.create(); - } else { - this.destroy(); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(WORKBENCH_TIPS_ENABLED_KEY)) { + const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); + if (enabled !== this.enabled) { + this.enabled = enabled; + if (this.enabled) { + this.create(); + } else { + this.destroy(); + } } } })); diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 93be1d9e534..dff17826c3b 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -19,8 +19,7 @@ import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { TPromise } from 'vs/base/common/winjs.base'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -83,9 +82,9 @@ export class WelcomePageContribution implements IWorkbenchContribution { } function isWelcomePageEnabled(configurationService: IConfigurationService) { - const startupEditor = configurationService.lookup(configurationKey); + const startupEditor = configurationService.inspect(configurationKey); if (!startupEditor.user && !startupEditor.workspace) { - const welcomeEnabled = configurationService.lookup(oldConfigurationKey); + const welcomeEnabled = configurationService.inspect(oldConfigurationKey); if (welcomeEnabled.value !== undefined && welcomeEnabled.value !== null) { return welcomeEnabled.value; } @@ -237,7 +236,6 @@ class WelcomePage { @IWindowsService private windowsService: IWindowsService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IConfigurationService private configurationService: IConfigurationService, - @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, @IEnvironmentService private environmentService: IEnvironmentService, @IMessageService private messageService: IMessageService, @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, @@ -279,7 +277,7 @@ class WelcomePage { showOnStartup.setAttribute('checked', 'checked'); } showOnStartup.addEventListener('click', e => { - this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: configurationKey, value: showOnStartup.checked ? 'welcomePage' : 'newUntitledFile' }); + this.configurationService.updateValue(configurationKey, showOnStartup.checked ? 'welcomePage' : 'newUntitledFile', ConfigurationTarget.USER); }); recentlyOpened.then(({ workspaces }) => { diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts index 3795d6aabd1..8c3e14e3123 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts @@ -285,7 +285,7 @@ export class WalkThroughPart extends BaseEditor { } private getArrowScrollHeight() { - let fontSize = this.configurationService.lookup('editor.fontSize').value; + let fontSize = this.configurationService.getValue('editor.fontSize'); if (typeof fontSize !== 'number' || fontSize < 1) { fontSize = 12; } @@ -401,7 +401,7 @@ export class WalkThroughPart extends BaseEditor { } })); - this.contentDisposables.push(this.configurationService.onDidUpdateConfiguration(() => { + this.contentDisposables.push(this.configurationService.onDidChangeConfiguration(() => { if (snippet.textEditorModel) { editor.updateOptions(this.getEditorOptions(snippet.textEditorModel.getModeId())); } @@ -452,7 +452,7 @@ export class WalkThroughPart extends BaseEditor { }); this.updateSizeClasses(); this.multiCursorModifier(); - this.contentDisposables.push(this.configurationService.onDidUpdateConfiguration(() => this.multiCursorModifier())); + this.contentDisposables.push(this.configurationService.onDidChangeConfiguration(() => this.multiCursorModifier())); if (input.onReady) { input.onReady(innerContent); } @@ -510,8 +510,8 @@ export class WalkThroughPart extends BaseEditor { private multiCursorModifier() { const labels = UILabelProvider.modifierLabels[OS]; - const setting = this.configurationService.lookup('editor.multiCursorModifier'); - const modifier = labels[setting.value === 'ctrlCmd' ? (OS === OperatingSystem.Macintosh ? 'metaKey' : 'ctrlKey') : 'altKey']; + const value = this.configurationService.getValue('editor.multiCursorModifier'); + const modifier = labels[value === 'ctrlCmd' ? (OS === OperatingSystem.Macintosh ? 'metaKey' : 'ctrlKey') : 'altKey']; const keys = this.content.querySelectorAll('.multi-cursor-modifier'); Array.prototype.forEach.call(keys, (key: Element) => { while (key.firstChild) { diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts index 98a5ffc7c8d..a534a77b13b 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts @@ -15,6 +15,7 @@ import { marked } from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; export class WalkThroughModel extends EditorModel { @@ -63,7 +64,8 @@ export class WalkThroughInput extends EditorInput { private options: WalkThroughInputOptions, @ITelemetryService private telemetryService: ITelemetryService, @ILifecycleService lifecycleService: ILifecycleService, - @ITextModelService private textModelResolverService: ITextModelService + @ITextModelService private textModelResolverService: ITextModelService, + @IHashService private hashService: IHashService ) { super(); this.disposables.push(lifecycleService.onShutdown(e => this.disposeTelemetry(e))); @@ -92,7 +94,7 @@ export class WalkThroughInput extends EditorInput { getTelemetryDescriptor(): object { const descriptor = super.getTelemetryDescriptor(); descriptor['target'] = this.getTelemetryFrom(); - descriptor['resource'] = telemetryURIDescriptor(this.options.resource); + descriptor['resource'] = telemetryURIDescriptor(this.options.resource, path => this.hashService.createSHA1(path)); /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { "target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, diff --git a/src/vs/workbench/services/activity/common/activityBarService.ts b/src/vs/workbench/services/activity/common/activityBarService.ts index 35732c5c1c4..2d7d55ce74b 100644 --- a/src/vs/workbench/services/activity/common/activityBarService.ts +++ b/src/vs/workbench/services/activity/common/activityBarService.ts @@ -67,24 +67,4 @@ export interface IActivityBarService { * Show activity in the activitybar for the given viewlet or global action. */ showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string): IDisposable; - - /** - * Unpins a viewlet from the activitybar. - */ - unpin(viewletId: string): void; - - /** - * Pin a viewlet inside the activity bar. - */ - pin(viewletId: string): void; - - /** - * Find out if a viewlet is pinned in the activity bar. - */ - isPinned(viewletId: string): boolean; - - /** - * Reorder viewlet ordering by moving a viewlet to the location of another viewlet. - */ - move(viewletId: string, toViewletId: string): void; } diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 28c1b711fed..3d4eb1394b2 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -13,14 +13,17 @@ export const WORKSPACE_CONFIG_DEFAULT_PATH = `${WORKSPACE_CONFIG_FOLDER_DEFAULT_ export const IWorkspaceConfigurationService = createDecorator('configurationService'); export interface IWorkspaceConfigurationService extends IConfigurationService { - /** * Returns untrusted configuration keys for the current workspace. */ getUnsupportedWorkspaceKeys(): string[]; - } +export const defaultSettingsSchemaId = 'vscode://schemas/settings/default'; +export const userSettingsSchemaId = 'vscode://schemas/settings/user'; +export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; +export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; + export const TASKS_CONFIGURATION_KEY = 'tasks'; export const LAUNCH_CONFIGURATION_KEY = 'launch'; diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts deleted file mode 100644 index 5cd14ca3229..00000000000 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ /dev/null @@ -1,104 +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 { TPromise } from 'vs/base/common/winjs.base'; -import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; - -export const IConfigurationEditingService = createDecorator('configurationEditingService'); - -export enum ConfigurationEditingErrorCode { - - /** - * Error when trying to write a configuration key that is not registered. - */ - ERROR_UNKNOWN_KEY, - - /** - * Error when trying to write an invalid folder configuration key to folder settings. - */ - ERROR_INVALID_FOLDER_CONFIGURATION, - - /** - * Error when trying to write to user target but not supported for provided key. - */ - ERROR_INVALID_USER_TARGET, - - /** - * Error when trying to write a configuration key to folder target - */ - ERROR_INVALID_FOLDER_TARGET, - - /** - * Error when trying to write to the workspace configuration without having a workspace opened. - */ - ERROR_NO_WORKSPACE_OPENED, - - /** - * Error when trying to write and save to the configuration file while it is dirty in the editor. - */ - ERROR_CONFIGURATION_FILE_DIRTY, - - /** - * Error when trying to write to a configuration file that contains JSON errors. - */ - ERROR_INVALID_CONFIGURATION -} - -export class ConfigurationEditingError extends Error { - constructor(message: string, public code: ConfigurationEditingErrorCode) { - super(message); - } -} - -export enum ConfigurationTarget { - - /** - * Targets the user configuration file for writing. - */ - USER, - - /** - * Targets the workspace configuration file for writing. This only works if a workspace is opened. - */ - WORKSPACE, - - /** - * Targets the folder configuration file for writing. This only works if a workspace is opened. - */ - FOLDER -} - -export interface IConfigurationValue { - key: string; - value: any; -} - -export interface IConfigurationEditingOptions { - /** - * If `true`, do not saves the configuration. Default is `false`. - */ - donotSave?: boolean; - /** - * If `true`, do not notifies the error to user by showing the message box. Default is `false`. - */ - donotNotifyError?: boolean; - /** - * Scope of configuration to be written into. - */ - scopes?: IConfigurationOverrides; -} - -export interface IConfigurationEditingService { - - _serviceBrand: ServiceIdentifier; - - /** - * Allows to write the configuration value to either the user or workspace configuration file and save it if asked to save. - * The returned promise will be in error state in any of the error cases from [ConfigurationEditingErrorCode](#ConfigurationEditingErrorCode) - */ - writeConfiguration(target: ConfigurationTarget, value: IConfigurationValue, options?: IConfigurationEditingOptions): TPromise; -} \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts new file mode 100644 index 00000000000..7614f64f2e3 --- /dev/null +++ b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts @@ -0,0 +1,212 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as objects from 'vs/base/common/objects'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { workspaceSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; + +const configurationRegistry = Registry.as(Extensions.Configuration); + +const configurationEntrySchema: IJSONSchema = { + type: 'object', + defaultSnippets: [{ body: { title: '', properties: {} } }], + properties: { + title: { + description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'), + type: 'string' + }, + properties: { + description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'), + type: 'object', + additionalProperties: { + anyOf: [ + { $ref: 'http://json-schema.org/draft-04/schema#' }, + { + type: 'object', + properties: { + isExecutable: { + type: 'boolean' + }, + scope: { + type: 'string', + enum: ['window', 'resource'], + default: 'window', + enumDescriptions: [ + nls.localize('scope.window.description', "Window specific configuration, which can be configured in the User or Workspace settings."), + nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the User, Workspace or Folder settings.") + ], + description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `window` and `resource`.") + } + } + } + ] + } + } + } +}; + + +// BEGIN VSCode extension point `configuration` +const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configuration', [], { + description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'), + oneOf: [ + configurationEntrySchema, + { + type: 'array', + items: configurationEntrySchema + } + ] +}); +configurationExtPoint.setHandler(extensions => { + const configurations: IConfigurationNode[] = []; + + function handleConfiguration(node: IConfigurationNode, id: string, collector: ExtensionMessageCollector) { + let configuration = objects.clone(node); + + if (configuration.title && (typeof configuration.title !== 'string')) { + collector.error(nls.localize('invalid.title', "'configuration.title' must be a string")); + } + + validateProperties(configuration, collector); + + configuration.id = id; + configurations.push(configuration); + }; + + for (let extension of extensions) { + const collector = extension.collector; + const value = extension.value; + const id = extension.description.id; + if (!Array.isArray(value)) { + handleConfiguration(value, id, collector); + } else { + value.forEach(v => handleConfiguration(v, id, collector)); + } + } + configurationRegistry.registerConfigurations(configurations, false); +}); +// END VSCode extension point `configuration` + +// BEGIN VSCode extension point `configurationDefaults` +const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configurationDefaults', [], { + description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'), + type: 'object', + defaultSnippets: [{ body: {} }], + patternProperties: { + '\\[.*\\]$': { + type: 'object', + default: {}, + $ref: editorConfigurationSchemaId, + } + } +}); +defaultConfigurationExtPoint.setHandler(extensions => { + const defaultConfigurations: IDefaultConfigurationExtension[] = extensions.map(extension => { + const id = extension.description.id; + const name = extension.description.name; + const defaults = objects.clone(extension.value); + return { + id, name, defaults + }; + }); + configurationRegistry.registerDefaultConfigurations(defaultConfigurations); +}); +// END VSCode extension point `configurationDefaults` + +function validateProperties(configuration: IConfigurationNode, collector: ExtensionMessageCollector): void { + let properties = configuration.properties; + if (properties) { + if (typeof properties !== 'object') { + collector.error(nls.localize('invalid.properties', "'configuration.properties' must be an object")); + configuration.properties = {}; + } + for (let key in properties) { + const message = validateProperty(key); + const propertyConfiguration = configuration.properties[key]; + propertyConfiguration.scope = propertyConfiguration.scope && propertyConfiguration.scope.toString() === 'resource' ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW; + propertyConfiguration.isFromExtensions = true; + if (message) { + collector.warn(message); + delete properties[key]; + } + } + } + let subNodes = configuration.allOf; + if (subNodes) { + collector.error(nls.localize('invalid.allOf', "'configuration.allOf' is deprecated and should no longer be used. Instead, pass multiple configuration sections as an array to the 'configuration' contribution point.")); + for (let node of subNodes) { + validateProperties(node, collector); + } + } +} + +const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); +jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { + default: { + folders: [ + { + path: '' + } + ], + settings: { + } + }, + required: ['folders'], + properties: { + 'folders': { + minItems: 0, + uniqueItems: true, + description: nls.localize('workspaceConfig.folders.description', "List of folders to be loaded in the workspace."), + items: { + type: 'object', + default: { path: '' }, + oneOf: [{ + properties: { + path: { + type: 'string', + description: nls.localize('workspaceConfig.path.description', "A file path. e.g. `/root/folderA` or `./folderA` for a relative path that will be resolved against the location of the workspace file.") + }, + name: { + type: 'string', + description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") + } + }, + required: ['path'] + }, { + properties: { + uri: { + type: 'string', + description: nls.localize('workspaceConfig.uri.description', "URI of the folder") + }, + name: { + type: 'string', + description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") + } + }, + required: ['uri'] + }] + } + }, + 'settings': { + type: 'object', + default: {}, + description: nls.localize('workspaceConfig.settings.description', "Workspace settings"), + $ref: workspaceSettingsSchemaId + }, + 'extensions': { + type: 'object', + default: {}, + description: nls.localize('workspaceConfig.extensions.description', "Workspace extensions"), + $ref: 'vscode://schemas/extensions' + } + }, + additionalProperties: false, + errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property") +}); \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index dd7f73103ed..69fd5bd628a 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -4,22 +4,25 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { clone } from 'vs/base/common/objects'; -import { CustomConfigurationModel, toValuesTree } from 'vs/platform/configuration/common/model'; -import { ConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { clone, equals } from 'vs/base/common/objects'; +import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel, Configuration as BaseConfiguration, CustomConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { Workspace } from 'vs/platform/workspace/common/workspace'; +import { StrictResourceMap } from 'vs/base/common/map'; +import URI from 'vs/base/common/uri'; -export class WorkspaceConfigurationModel extends CustomConfigurationModel { +export class WorkspaceConfigurationModel extends CustomConfigurationModel { - private _raw: T; + private _raw: any; private _folders: IStoredWorkspaceFolder[]; - private _worksapaceSettings: ConfigurationModel; - private _tasksConfiguration: ConfigurationModel; - private _launchConfiguration: ConfigurationModel; - private _workspaceConfiguration: ConfigurationModel; + private _worksapaceSettings: ConfigurationModel; + private _tasksConfiguration: ConfigurationModel; + private _launchConfiguration: ConfigurationModel; + private _workspaceConfiguration: ConfigurationModel; public update(content: string): void { super.update(content); @@ -31,11 +34,11 @@ export class WorkspaceConfigurationModel extends CustomConfigurationModel return this._folders; } - get workspaceConfiguration(): ConfigurationModel { + get workspaceConfiguration(): ConfigurationModel { return this._workspaceConfiguration; } - protected processRaw(raw: T): void { + protected processRaw(raw: any): void { this._raw = raw; this._folders = (this._raw['folders'] || []) as IStoredWorkspaceFolder[]; @@ -46,27 +49,27 @@ export class WorkspaceConfigurationModel extends CustomConfigurationModel super.processRaw(raw); } - private parseConfigurationModel(section: string): ConfigurationModel { + private parseConfigurationModel(section: string): ConfigurationModel { const rawSection = this._raw[section] || {}; const contents = toValuesTree(rawSection, message => console.error(`Conflict in section '${section}' of workspace configuration file ${message}`)); - return new ConfigurationModel(contents, Object.keys(rawSection)); + return new ConfigurationModel(contents, Object.keys(rawSection)); } - private consolidate(): ConfigurationModel { + private consolidate(): ConfigurationModel { const keys: string[] = [...this._worksapaceSettings.keys, ...this._tasksConfiguration.keys.map(key => `tasks.${key}`), ...this._launchConfiguration.keys.map(key => `launch.${key}`)]; - const mergedContents = new ConfigurationModel({}, keys) + const mergedContents = new ConfigurationModel({}, keys) .merge(this._worksapaceSettings) .merge(this._tasksConfiguration) .merge(this._launchConfiguration); - return new ConfigurationModel(mergedContents.contents, keys, mergedContents.overrides); + return new ConfigurationModel(mergedContents.contents, keys, mergedContents.overrides); } } -export class ScopedConfigurationModel extends CustomConfigurationModel { +export class ScopedConfigurationModel extends CustomConfigurationModel { constructor(content: string, name: string, public readonly scope: string) { super(null, name); @@ -82,14 +85,14 @@ export class ScopedConfigurationModel extends CustomConfigurationModel { } -export class FolderSettingsModel extends CustomConfigurationModel { +export class FolderSettingsModel extends CustomConfigurationModel { - private _raw: T; + private _raw: any; private _unsupportedKeys: string[]; - protected processRaw(raw: T): void { + protected processRaw(raw: any): void { this._raw = raw; - const processedRaw = {}; + const processedRaw = {}; this._unsupportedKeys = []; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); for (let key in raw) { @@ -118,16 +121,16 @@ export class FolderSettingsModel extends CustomConfigurationModel { return !propertySchema.isExecutable; } - public createWorkspaceConfigurationModel(): ConfigurationModel { + public createWorkspaceConfigurationModel(): ConfigurationModel { return this.createScopedConfigurationModel(ConfigurationScope.WINDOW); } - public createFolderScopedConfigurationModel(): ConfigurationModel { + public createFolderScopedConfigurationModel(): ConfigurationModel { return this.createScopedConfigurationModel(ConfigurationScope.RESOURCE); } - private createScopedConfigurationModel(scope: ConfigurationScope): ConfigurationModel { - const workspaceRaw = {}; + private createScopedConfigurationModel(scope: ConfigurationScope): ConfigurationModel { + const workspaceRaw = {}; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); for (let key in this._raw) { if (this.getScope(key, configurationProperties) === scope) { @@ -145,15 +148,15 @@ export class FolderSettingsModel extends CustomConfigurationModel { } } -export class FolderConfigurationModel extends CustomConfigurationModel { +export class FolderConfigurationModel extends CustomConfigurationModel { - constructor(public readonly workspaceSettingsConfig: FolderSettingsModel, private scopedConfigs: ScopedConfigurationModel[], private scope: ConfigurationScope) { + constructor(public readonly workspaceSettingsConfig: FolderSettingsModel, private scopedConfigs: ScopedConfigurationModel[], private scope: ConfigurationScope) { super(); this.consolidate(); } private consolidate(): void { - this._contents = {}; + this._contents = {}; this._overrides = []; this.doMerge(this, ConfigurationScope.WINDOW === this.scope ? this.workspaceSettingsConfig : this.workspaceSettingsConfig.createFolderScopedConfigurationModel()); @@ -178,4 +181,129 @@ export class FolderConfigurationModel extends CustomConfigurationModel { this.workspaceSettingsConfig.reprocess(); this.consolidate(); } +} + +export class Configuration extends BaseConfiguration { + + constructor( + defaults: ConfigurationModel, + user: ConfigurationModel, + workspaceConfiguration: ConfigurationModel, + protected folders: StrictResourceMap, + memoryConfiguration: ConfigurationModel, + memoryConfigurationByResource: StrictResourceMap, + workspace: Workspace) { + super(defaults, user, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource, workspace); + } + + updateDefaultConfiguration(defaults: ConfigurationModel): void { + this._defaults = defaults; + this.merge(); + } + + updateUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent { + const { added, updated, removed } = compare(this._user, user); + let changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); + + this._user = user; + this.merge(); + + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); + } + return new ConfigurationChangeEvent().change(changedKeys); + } + + updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): ConfigurationChangeEvent { + const { added, updated, removed } = compare(this._workspaceConfiguration, workspaceConfiguration); + let changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); + + this._workspaceConfiguration = workspaceConfiguration; + this.merge(); + + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key), this.getValue(key))); + } + return new ConfigurationChangeEvent().change(changedKeys); + } + + updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel): ConfigurationChangeEvent { + const currentFolderConfiguration = this.folders.get(resource); + + if (currentFolderConfiguration) { + const { added, updated, removed } = compare(currentFolderConfiguration, configuration); + let changedKeys = [...added, ...updated, ...removed]; + if (changedKeys.length) { + const oldConfiguartion = new Configuration(this._defaults, this._user, this._workspaceConfiguration, this.folders, this._memoryConfiguration, this._memoryConfigurationByResource, this._workspace); + + this.folders.set(resource, configuration); + this.mergeFolder(resource); + + changedKeys = changedKeys.filter(key => !equals(oldConfiguartion.getValue(key, { resource }), this.getValue(key, { resource }))); + } + return new ConfigurationChangeEvent().change(changedKeys, resource); + } + + this.folders.set(resource, configuration); + this.mergeFolder(resource); + return new ConfigurationChangeEvent().change(configuration.keys, resource); + } + + deleteFolderConfiguration(folder: URI): ConfigurationChangeEvent { + if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) { + // Do not remove workspace configuration + return new ConfigurationChangeEvent(); + } + + const keys = this.folders.get(folder).keys; + this.folders.delete(folder); + this._foldersConsolidatedConfigurations.delete(folder); + return new ConfigurationChangeEvent().change(keys, folder); + } + + getFolderConfigurationModel(folder: URI): FolderConfigurationModel { + return this.folders.get(folder); + } +} + +export class WorkspaceConfigurationChangeEvent implements IConfigurationChangeEvent { + + constructor(private configurationChangeEvent: IConfigurationChangeEvent, private workspace: Workspace) { } + + get changedConfiguration(): IConfigurationModel { + return this.configurationChangeEvent.changedConfiguration; + } + + get changedConfigurationByResource(): StrictResourceMap { + return this.configurationChangeEvent.changedConfigurationByResource; + } + + get affectedKeys(): string[] { + return this.configurationChangeEvent.affectedKeys; + } + + get source(): ConfigurationTarget { + return this.configurationChangeEvent.source; + } + + get sourceConfig(): any { + return this.configurationChangeEvent.sourceConfig; + } + + affectsConfiguration(config: string, resource?: URI): boolean { + if (this.configurationChangeEvent.affectsConfiguration(config, resource)) { + return true; + } + + if (resource && this.workspace) { + let workspaceFolder = this.workspace.getFolder(resource); + if (workspaceFolder) { + return this.configurationChangeEvent.affectsConfiguration(config, workspaceFolder.uri); + } + } + + return false; + } } \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index 9787cfcbc1c..e4b1cd4e7a0 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -23,17 +23,79 @@ import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { keyFromOverrideIdentifier } from 'vs/platform/configuration/common/model'; +import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; -import { IConfigurationEditingService, ConfigurationEditingErrorCode, ConfigurationEditingError, ConfigurationTarget, IConfigurationValue, IConfigurationEditingOptions } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IChoiceService, IMessageService, Severity } from 'vs/platform/message/common/message'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +export enum ConfigurationEditingErrorCode { + + /** + * Error when trying to write a configuration key that is not registered. + */ + ERROR_UNKNOWN_KEY, + + /** + * Error when trying to write an invalid folder configuration key to folder settings. + */ + ERROR_INVALID_FOLDER_CONFIGURATION, + + /** + * Error when trying to write to user target but not supported for provided key. + */ + ERROR_INVALID_USER_TARGET, + + /** + * Error when trying to write a configuration key to folder target + */ + ERROR_INVALID_FOLDER_TARGET, + + /** + * Error when trying to write to the workspace configuration without having a workspace opened. + */ + ERROR_NO_WORKSPACE_OPENED, + + /** + * Error when trying to write and save to the configuration file while it is dirty in the editor. + */ + ERROR_CONFIGURATION_FILE_DIRTY, + + /** + * Error when trying to write to a configuration file that contains JSON errors. + */ + ERROR_INVALID_CONFIGURATION +} + +export class ConfigurationEditingError extends Error { + constructor(message: string, public code: ConfigurationEditingErrorCode) { + super(message); + } +} + +export interface IConfigurationValue { + key: string; + value: any; +} + +export interface IConfigurationEditingOptions { + /** + * If `true`, do not saves the configuration. Default is `false`. + */ + donotSave?: boolean; + /** + * If `true`, do not notifies the error to user by showing the message box. Default is `false`. + */ + donotNotifyError?: boolean; + /** + * Scope of configuration to be written into. + */ + scopes?: IConfigurationOverrides; +} + interface IConfigurationEditOperation extends IConfigurationValue { target: ConfigurationTarget; jsonPath: json.JSONPath; @@ -51,7 +113,7 @@ interface ConfigurationEditingOptions extends IConfigurationEditingOptions { force?: boolean; } -export class ConfigurationEditingService implements IConfigurationEditingService { +export class ConfigurationEditingService { public _serviceBrand: any; @@ -95,9 +157,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService private writeToBuffer(model: editorCommon.IModel, operation: IConfigurationEditOperation, save: boolean): TPromise { const edit = this.getEdits(model, operation)[0]; if (this.applyEditsToBuffer(edit, model) && save) { - return this.textFileService.save(operation.resource, { skipSaveParticipants: true /* programmatic change */ }) - // Reload the configuration so that we make sure all parties are updated - .then(() => this.configurationService.reloadConfiguration()); + return this.textFileService.save(operation.resource, { skipSaveParticipants: true /* programmatic change */ }); } return TPromise.as(null); } @@ -193,7 +253,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService case ConfigurationTarget.WORKSPACE: this.commandService.executeCommand('workbench.action.openWorkspaceSettings'); break; - case ConfigurationTarget.FOLDER: + case ConfigurationTarget.WORKSPACE_FOLDER: if (operation.resource) { const workspaceFolder = this.contextService.getWorkspaceFolder(operation.resource); if (workspaceFolder) { @@ -237,7 +297,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService return nls.localize('errorInvalidConfiguration', "Unable to write into user settings. Please open **User Settings** file to correct errors/warnings in it and try again."); case ConfigurationTarget.WORKSPACE: return nls.localize('errorInvalidConfigurationWorkspace', "Unable to write into workspace settings. Please open **Workspace Settings** file to correct errors/warnings in the file and try again."); - case ConfigurationTarget.FOLDER: + case ConfigurationTarget.WORKSPACE_FOLDER: const workspaceFolderName = this.contextService.getWorkspaceFolder(operation.resource).name; return nls.localize('errorInvalidConfigurationFolder', "Unable to write into folder settings. Please open **Folder Settings** file under **{0}** folder to correct errors/warnings in it and try again.", workspaceFolderName); } @@ -255,7 +315,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService return nls.localize('errorConfigurationFileDirty', "Unable to write into user settings because the file is dirty. Please save the **User Settings** file and try again."); case ConfigurationTarget.WORKSPACE: return nls.localize('errorConfigurationFileDirtyWorkspace', "Unable to write into workspace settings because the file is dirty. Please save the **Workspace Settings** file and try again."); - case ConfigurationTarget.FOLDER: + case ConfigurationTarget.WORKSPACE_FOLDER: const workspaceFolderName = this.contextService.getWorkspaceFolder(operation.resource).name; return nls.localize('errorConfigurationFileDirtyFolder', "Unable to write into folder settings because the file is dirty. Please save the **Folder Settings** file under **{0}** folder and try again.", workspaceFolderName); } @@ -270,9 +330,10 @@ export class ConfigurationEditingService implements IConfigurationEditingService return nls.localize('userTarget', "User Settings"); case ConfigurationTarget.WORKSPACE: return nls.localize('workspaceTarget', "Workspace Settings"); - case ConfigurationTarget.FOLDER: + case ConfigurationTarget.WORKSPACE_FOLDER: return nls.localize('folderTarget', "Folder Settings"); } + return ''; } private getEdits(model: editorCommon.IModel, edit: IConfigurationEditOperation): Edit[] { @@ -328,11 +389,11 @@ export class ConfigurationEditingService implements IConfigurationEditingService } // Target cannot be workspace or folder if no workspace opened - if ((target === ConfigurationTarget.WORKSPACE || target === ConfigurationTarget.FOLDER) && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + if ((target === ConfigurationTarget.WORKSPACE || target === ConfigurationTarget.WORKSPACE_FOLDER) && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { return this.wrapError(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target, operation); } - if (target === ConfigurationTarget.FOLDER) { + if (target === ConfigurationTarget.WORKSPACE_FOLDER) { if (!operation.resource) { return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } @@ -419,7 +480,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService } } - if (target === ConfigurationTarget.FOLDER) { + if (target === ConfigurationTarget.WORKSPACE_FOLDER) { if (resource) { const folder = this.contextService.getWorkspaceFolder(resource); if (folder) { diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configurationService.ts similarity index 53% rename from src/vs/workbench/services/configuration/node/configuration.ts rename to src/vs/workbench/services/configuration/node/configurationService.ts index 74556f8b5ab..262151b6a7b 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -7,10 +7,11 @@ import URI from 'vs/base/common/uri'; import * as paths from 'vs/base/common/paths'; import { TPromise } from 'vs/base/common/winjs.base'; +import * as assert from 'vs/base/common/assert'; import Event, { Emitter } from 'vs/base/common/event'; import { StrictResourceMap } from 'vs/base/common/map'; -import * as objects from 'vs/base/common/objects'; import * as errors from 'vs/base/common/errors'; +import { equals } from 'vs/base/common/objects'; import * as collections from 'vs/base/common/collections'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -22,28 +23,22 @@ import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files import { isLinux } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CustomConfigurationModel } from 'vs/platform/configuration/common/model'; -import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels'; -import { IConfigurationServiceEvent, ConfigurationSource, IConfigurationKeys, IConfigurationValue, ConfigurationModel, IConfigurationOverrides, Configuration as BaseConfiguration, IConfigurationValues, IConfigurationData } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; +import { CustomConfigurationModel, ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; +import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { ConfigurationService as GlobalConfigurationService } from 'vs/platform/configuration/node/configurationService'; -import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry'; import { createHash } from 'crypto'; import { getWorkspaceLabel, IWorkspacesService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; - -const defaultSettingsSchemaId = 'vscode://schemas/settings/default'; -const userSettingsSchemaId = 'vscode://schemas/settings/user'; -const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; -const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; interface IStat { resource: URI; @@ -56,224 +51,18 @@ interface IContent { value: string; } -interface IWorkspaceConfiguration { - workspace: T; - consolidated: any; -} - -type IWorkspaceFoldersConfiguration = { [rootFolder: string]: { folders: string[]; } }; - -const configurationRegistry = Registry.as(Extensions.Configuration); - -const configurationEntrySchema: IJSONSchema = { - type: 'object', - defaultSnippets: [{ body: { title: '', properties: {} } }], - properties: { - title: { - description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'), - type: 'string' - }, - properties: { - description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'), - type: 'object', - additionalProperties: { - anyOf: [ - { $ref: 'http://json-schema.org/draft-04/schema#' }, - { - type: 'object', - properties: { - isExecutable: { - type: 'boolean' - }, - scope: { - type: 'string', - enum: ['window', 'resource'], - default: 'window', - enumDescriptions: [ - nls.localize('scope.window.description', "Window specific configuration, which can be configured in the User or Workspace settings."), - nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the User, Workspace or Folder settings.") - ], - description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `window` and `resource`.") - } - } - } - ] - } - } - } -}; - - -// BEGIN VSCode extension point `configuration` -const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configuration', [], { - description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'), - oneOf: [ - configurationEntrySchema, - { - type: 'array', - items: configurationEntrySchema - } - ] -}); -configurationExtPoint.setHandler(extensions => { - const configurations: IConfigurationNode[] = []; - - function handleConfiguration(node: IConfigurationNode, id: string, collector: ExtensionMessageCollector) { - let configuration = objects.clone(node); - - if (configuration.title && (typeof configuration.title !== 'string')) { - collector.error(nls.localize('invalid.title', "'configuration.title' must be a string")); - } - - validateProperties(configuration, collector); - - configuration.id = id; - configurations.push(configuration); - }; - - for (let extension of extensions) { - const collector = extension.collector; - const value = extension.value; - const id = extension.description.id; - if (!Array.isArray(value)) { - handleConfiguration(value, id, collector); - } else { - value.forEach(v => handleConfiguration(v, id, collector)); - } - } - configurationRegistry.registerConfigurations(configurations, false); -}); -// END VSCode extension point `configuration` - -// BEGIN VSCode extension point `configurationDefaults` -const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint('configurationDefaults', [], { - description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'), - type: 'object', - defaultSnippets: [{ body: {} }], - patternProperties: { - '\\[.*\\]$': { - type: 'object', - default: {}, - $ref: editorConfigurationSchemaId, - } - } -}); -defaultConfigurationExtPoint.setHandler(extensions => { - const defaultConfigurations: IDefaultConfigurationExtension[] = extensions.map(extension => { - const id = extension.description.id; - const name = extension.description.name; - const defaults = objects.clone(extension.value); - return { - id, name, defaults - }; - }); - configurationRegistry.registerDefaultConfigurations(defaultConfigurations); -}); -// END VSCode extension point `configurationDefaults` - -function validateProperties(configuration: IConfigurationNode, collector: ExtensionMessageCollector): void { - let properties = configuration.properties; - if (properties) { - if (typeof properties !== 'object') { - collector.error(nls.localize('invalid.properties', "'configuration.properties' must be an object")); - configuration.properties = {}; - } - for (let key in properties) { - const message = validateProperty(key); - const propertyConfiguration = configuration.properties[key]; - propertyConfiguration.scope = propertyConfiguration.scope && propertyConfiguration.scope.toString() === 'resource' ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW; - propertyConfiguration.isFromExtensions = true; - if (message) { - collector.warn(message); - delete properties[key]; - } - } - } - let subNodes = configuration.allOf; - if (subNodes) { - collector.error(nls.localize('invalid.allOf', "'configuration.allOf' is deprecated and should no longer be used. Instead, pass multiple configuration sections as an array to the 'configuration' contribution point.")); - for (let node of subNodes) { - validateProperties(node, collector); - } - } -} - -const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); -contributionRegistry.registerSchema('vscode://schemas/workspaceConfig', { - default: { - folders: [ - { - path: '' - } - ], - settings: { - } - }, - required: ['folders'], - properties: { - 'folders': { - minItems: 0, - uniqueItems: true, - description: nls.localize('workspaceConfig.folders.description', "List of folders to be loaded in the workspace."), - items: { - type: 'object', - default: { path: '' }, - oneOf: [{ - properties: { - path: { - type: 'string', - description: nls.localize('workspaceConfig.path.description', "A file path. e.g. `/root/folderA` or `./folderA` for a relative path that will be resolved against the location of the workspace file.") - }, - name: { - type: 'string', - description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") - } - }, - required: ['path'] - }, { - properties: { - uri: { - type: 'string', - description: nls.localize('workspaceConfig.uri.description', "URI of the folder") - }, - name: { - type: 'string', - description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ") - } - }, - required: ['uri'] - }] - } - }, - 'settings': { - type: 'object', - default: {}, - description: nls.localize('workspaceConfig.settings.description', "Workspace settings"), - $ref: workspaceSettingsSchemaId - }, - 'extensions': { - type: 'object', - default: {}, - description: nls.localize('workspaceConfig.extensions.description', "Workspace extensions"), - $ref: 'vscode://schemas/extensions' - } - }, - additionalProperties: false, - errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property") -}); - export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService { public _serviceBrand: any; private workspace: Workspace; - private _configuration: Configuration; - private baseConfigurationService: GlobalConfigurationService; + private _configuration: Configuration; + private baseConfigurationService: GlobalConfigurationService; private workspaceConfiguration: WorkspaceConfiguration; - private cachedFolderConfigs: StrictResourceMap>; + private cachedFolderConfigs: StrictResourceMap; - protected readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; protected readonly _onDidChangeWorkspaceFolders: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkspaceFolders: Event = this._onDidChangeWorkspaceFolders.event; @@ -284,6 +73,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat protected readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkbenchState: Event = this._onDidChangeWorkbenchState.event; + private configurationEditingService: ConfigurationEditingService; + constructor(private environmentService: IEnvironmentService, private workspacesService: IWorkspacesService, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) { super(); @@ -291,10 +82,12 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged())); this.baseConfigurationService = this._register(new GlobalConfigurationService(environmentService)); - this._register(this.baseConfigurationService.onDidUpdateConfiguration(e => this.onBaseConfigurationChanged(e))); - this._register(configurationRegistry.onDidRegisterConfiguration(e => this.registerConfigurationSchemas())); + this._register(this.baseConfigurationService.onDidChangeConfiguration(e => this.onBaseConfigurationChanged(e))); + this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(e => this.registerConfigurationSchemas())); } + // Workspace Context Service Impl + public getWorkspace(): Workspace { return this.workspace; } @@ -332,64 +125,108 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return false; } - public getConfigurationData(): IConfigurationData { + // Workspace Configuration Service Impl + + getConfigurationData(): IConfigurationData { return this._configuration.toData(); } - public getConfiguration(section?: string, overrides?: IConfigurationOverrides): C { - return this._configuration.getValue(section, overrides); + getConfiguration(): T + getConfiguration(section: string): T + getConfiguration(overrides: IConfigurationOverrides): T + getConfiguration(section: string, overrides: IConfigurationOverrides): T + getConfiguration(arg1?: any, arg2?: any): any { + const section = typeof arg1 === 'string' ? arg1 : void 0; + const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0; + return this._configuration.getSection(section, overrides); } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { - return this._configuration.lookup(key, overrides); + getValue(key: string, overrides?: IConfigurationOverrides): T { + return this._configuration.getValue(key, overrides); } - public keys(overrides?: IConfigurationOverrides): IConfigurationKeys { - return this._configuration.keys(overrides); + updateValue(key: string, value: any): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise + updateValue(key: string, value: any, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise + updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): TPromise { + assert.ok(this.configurationEditingService, 'Workbench is not initialized yet'); + const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0; + const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); + return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError) + : TPromise.as(null); } - public values(): IConfigurationValues { - return this._configuration.values(); + reloadConfiguration(folder?: IWorkspaceFolder, key?: string): TPromise { + if (folder) { + return this.reloadWorkspaceFolderConfiguration(folder, key); + } + return this.reloadUserConfiguration() + .then(() => this.loadConfiguration()); } - public reloadConfiguration(section?: string): TPromise { - const current = this._configuration; - // Reload and reinitialize to ensure we are hitting the disk - return this.baseConfigurationService.reloadConfiguration() - .then(() => { - if (this.workspace.configuration) { - return this.workspaceConfiguration.load(this.workspace.configuration) - .then(() => this.initializeConfiguration(false)); - } - return this.initializeConfiguration(false); - }) - .then(() => { - // Check and trigger - if (!this._configuration.equals(current)) { - this.triggerConfigurationChange(); - } - return this.getConfiguration(section); - }); + inspect(key: string, overrides?: IConfigurationOverrides): { + default: T, + user: T, + workspace: T, + workspaceFolder: T, + memory?: T, + value: T + } { + return this._configuration.lookup(key); } - public getUnsupportedWorkspaceKeys(): string[] { + keys(): { + default: string[]; + user: string[]; + workspace: string[]; + workspaceFolder: string[]; + } { + return this._configuration.keys(); + } + + getUnsupportedWorkspaceKeys(): string[] { return this.getWorkbenchState() === WorkbenchState.FOLDER ? this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri).workspaceSettingsConfig.unsupportedKeys : []; } - public handleWorkspaceFileEvents(event: FileChangesEvent): void { - TPromise.join(this.workspace.folders.map(folder => this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event))) // handle file event for each folder - .then(folderConfigurations => - folderConfigurations.map((configuration, index) => ({ configuration, folder: this.workspace.folders[index] })) - .filter(folderConfiguration => !!folderConfiguration.configuration) // Filter folders which are not impacted by events - .map(folderConfiguration => this.updateFolderConfiguration(folderConfiguration.folder, folderConfiguration.configuration, true)) // Update the configuration of impacted folders - .reduce((result, value) => result || value, false)) // Check if the effective configuration of folder is changed - .then(changed => changed ? this.triggerConfigurationChange() : void 0); // Trigger event if changed + reloadUserConfiguration(key?: string): TPromise { + return this.baseConfigurationService.reloadConfiguration(); } - public initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise { + reloadWorkspaceConfiguration(key?: string): TPromise { + const workbenchState = this.getWorkbenchState(); + if (workbenchState === WorkbenchState.FOLDER) { + return this.onWorkspaceFolderConfigurationChanged(this.workspace.folders[0], key); + } + if (workbenchState === WorkbenchState.WORKSPACE) { + return this.onWorkspaceConfigurationChanged(); + } + return TPromise.as(null); + } + + reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder, key?: string): TPromise { + return this.onWorkspaceFolderConfigurationChanged(folder, key); + } + + initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise { return this.createWorkspace(arg) .then(workspace => this.setWorkspace(workspace)) - .then(() => this.initializeConfiguration(true)); + .then(() => this.initializeConfiguration()); + } + + setInstantiationService(instantiationService: IInstantiationService): void { + this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService); + } + + handleWorkspaceFileEvents(event: FileChangesEvent): void { + switch (this.getWorkbenchState()) { + case WorkbenchState.FOLDER: + this.onSingleFolderFileChanges(event); + return; + case WorkbenchState.WORKSPACE: + this.onWorkspaceFileChanges(event); + return; + } } private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise { @@ -470,137 +307,233 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return result; } - private initializeConfiguration(trigger: boolean = true): TPromise { + private initializeConfiguration(): TPromise { this.registerConfigurationSchemas(); - this.resetCaches(); - return this.updateConfiguration() - .then(() => { - if (trigger) { - this.triggerConfigurationChange(); - } + return this.loadConfiguration(); + } + + private loadConfiguration(): TPromise { + // reset caches + this.cachedFolderConfigs = new StrictResourceMap(); + + const folders = this.workspace.folders; + return this.loadFolderConfigurations(folders) + .then((folderConfigurations) => { + + let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); + const folderConfigurationModels = new StrictResourceMap(); + folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); + + this._configuration = new Configuration(this.baseConfigurationService.configuration.defaults, this.baseConfigurationService.configuration.user, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null + // TODO Sandy: compare with old values?? + + const keys = this._configuration.keys(); + this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent([...keys.default, ...keys.user, ...keys.workspace, ...keys.workspaceFolder], ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); }); } - private resetCaches(): void { - this.cachedFolderConfigs = new StrictResourceMap>(); - this._configuration = new Configuration(this.baseConfigurationService.configuration(), new ConfigurationModel(), new StrictResourceMap>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null - this.initCachesForFolders(this.workspace.folders); - } - - private initCachesForFolders(folders: IWorkspaceFolder[]): void { - for (const folder of folders) { - this.cachedFolderConfigs.set(folder.uri, this._register(new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState() === WorkbenchState.WORKSPACE ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW))); - this.updateFolderConfiguration(folder, new FolderConfigurationModel(new FolderSettingsModel(null), [], ConfigurationScope.RESOURCE), false); + private getWorkspaceConfigurationModel(folderConfigurations: FolderConfigurationModel[]): ConfigurationModel { + switch (this.getWorkbenchState()) { + case WorkbenchState.FOLDER: + return folderConfigurations[0]; + case WorkbenchState.WORKSPACE: + return this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration; + default: + return new ConfigurationModel(); } } - private updateConfiguration(folders: IWorkspaceFolder[] = this.workspace.folders): TPromise { - return TPromise.join([...folders.map(folder => this.cachedFolderConfigs.get(folder.uri).loadConfiguration() - .then(configuration => this.updateFolderConfiguration(folder, configuration, true)))]) - .then(changed => changed.reduce((result, value) => result || value, false)) - .then(changed => this.updateWorkspaceConfiguration(true) || changed); - } - private registerConfigurationSchemas(): void { if (this.workspace) { - - contributionRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema); - contributionRegistry.registerSchema(userSettingsSchemaId, settingsSchema); + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + jsonRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(userSettingsSchemaId, settingsSchema); if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) { - contributionRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); - contributionRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema); + jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema); } else { - contributionRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); - contributionRegistry.registerSchema(folderSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(folderSettingsSchemaId, settingsSchema); } } } - private onBaseConfigurationChanged({ source, sourceConfig }: IConfigurationServiceEvent): void { - if (this.workspace) { - if (source === ConfigurationSource.Default) { + private onBaseConfigurationChanged(e: IConfigurationChangeEvent): void { + if (this.workspace && this._configuration) { + if (e.source === ConfigurationTarget.DEFAULT) { this.workspace.folders.forEach(folder => this._configuration.getFolderConfigurationModel(folder.uri).update()); - } - if (this._configuration.updateBaseConfiguration(this.baseConfigurationService.configuration())) { - this._onDidUpdateConfiguration.fire({ source, sourceConfig }); + this._configuration.updateDefaultConfiguration(this.baseConfigurationService.configuration.defaults); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change(e.affectedKeys), e.source); + } else { + let keys = this._configuration.updateUserConfiguration(this.baseConfigurationService.configuration.user); + this.triggerConfigurationChange(keys, e.source); } } } - private onWorkspaceConfigurationChanged(): void { - if (this.workspace && this.workspace.configuration) { + private onWorkspaceConfigurationChanged(): TPromise { + if (this.workspace && this.workspace.configuration && this._configuration) { + const workspaceConfigurationChangeEvent = this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration); let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.workspaceConfigurationModel.folders, URI.file(paths.dirname(this.workspace.configuration.fsPath))); const changes = this.compareFolders(this.workspace.folders, configuredFolders); - if (changes.added.length || changes.removed.length || changes.changed.length) { // TODO@Sandeep be smarter here about detecting changes + if (changes.added.length || changes.removed.length || changes.changed.length) { this.workspace.folders = configuredFolders; - this.onFoldersChanged() - .then(configurationChanged => { - if (configurationChanged) { - this.triggerConfigurationChange(); - } + return this.onFoldersChanged() + .then(foldersConfigurationChangeEvent => { + this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER); this._onDidChangeWorkspaceFolders.fire(changes); }); } else { - const configurationChanged = this.updateWorkspaceConfiguration(true); - if (configurationChanged) { - this.triggerConfigurationChange(); - } + this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE); } } + return TPromise.as(null); } - private onFoldersChanged(): TPromise { - let configurationChangedOnRemoval = false; + private onWorkspaceFileChanges(event: FileChangesEvent): TPromise { + return TPromise.join(this.workspace.folders.map(folder => + // handle file event for each folder + this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event) + // Update folder configuration if handled + .then(folderConfiguration => folderConfiguration ? this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration) : new ConfigurationChangeEvent())) + ).then(changeEvents => { + const consolidateChangeEvent = changeEvents.reduce((consolidated, e) => consolidated.change(e), new ConfigurationChangeEvent()); + this.triggerConfigurationChange(consolidateChangeEvent, ConfigurationTarget.WORKSPACE_FOLDER); + }); + } + + private onSingleFolderFileChanges(event: FileChangesEvent): TPromise { + const folder = this.workspace.folders[0]; + return this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event) + .then(folderConfiguration => { + if (folderConfiguration) { + // File change handled + this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration); + const workspaceChangedKeys = this._configuration.updateWorkspaceConfiguration(folderConfiguration); + this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE); + } + }); + } + + private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): TPromise { + this.disposeFolderConfiguration(folder); + return this.loadFolderConfigurations([folder]) + .then(([folderConfiguration]) => { + const folderChangedKeys = this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration); + if (this.getWorkbenchState() === WorkbenchState.FOLDER) { + const workspaceChangedKeys = this._configuration.updateWorkspaceConfiguration(folderConfiguration); + this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE); + } else { + this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER); + } + }); + } + + private onFoldersChanged(): TPromise { + let changeEvent = new ConfigurationChangeEvent(); // Remove the configurations of deleted folders for (const key of this.cachedFolderConfigs.keys()) { if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) { this.cachedFolderConfigs.delete(key); - if (this._configuration.deleteFolderConfiguration(key)) { - configurationChangedOnRemoval = true; - } + changeEvent = changeEvent.change(this._configuration.deleteFolderConfiguration(key)); } } - // Initialize the newly added folders const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri)); if (toInitialize.length) { - this.initCachesForFolders(toInitialize); - return this.updateConfiguration(toInitialize) - .then(changed => configurationChangedOnRemoval || changed); - } else if (configurationChangedOnRemoval) { - this.updateWorkspaceConfiguration(false); - return TPromise.as(true); + return this.loadFolderConfigurations(toInitialize) + .then(folderConfigurations => { + folderConfigurations.forEach((folderConfiguration, index) => { + changeEvent = changeEvent.change(this._configuration.updateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); + }); + return changeEvent; + }); } - return TPromise.as(false); + return TPromise.as(changeEvent); } - private updateFolderConfiguration(folder: IWorkspaceFolder, folderConfiguration: FolderConfigurationModel, compare: boolean): boolean { - let configurationChanged = this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration, compare); - if (this.getWorkbenchState() === WorkbenchState.FOLDER) { - // Workspace configuration changed - configurationChanged = this.updateWorkspaceConfiguration(compare) || configurationChanged; - } - return configurationChanged; + private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise { + return TPromise.join([...folders.map(folder => { + const folderConfiguration = new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState() === WorkbenchState.WORKSPACE ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW); + this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); + return folderConfiguration.loadConfiguration(); + })]); } - private updateWorkspaceConfiguration(compare: boolean): boolean { - const workbennchState = this.getWorkbenchState(); - if (workbennchState === WorkbenchState.EMPTY) { - return false; + private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides, donotNotifyError: boolean): TPromise { + if (target === ConfigurationTarget.DEFAULT) { + return TPromise.wrapError(new Error('Invalid configuration target')); } - const workspaceConfiguration = workbennchState === WorkbenchState.WORKSPACE ? this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration : this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri); - return this._configuration.updateWorkspaceConfiguration(workspaceConfiguration, compare); + if (target === ConfigurationTarget.MEMORY) { + this._configuration.updateValue(key, value, overrides); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides.resource), target); + return TPromise.as(null); + } + + return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError }) + .then(() => { + switch (target) { + case ConfigurationTarget.USER: + return this.reloadUserConfiguration(); + case ConfigurationTarget.WORKSPACE: + return this.reloadWorkspaceConfiguration(); + case ConfigurationTarget.WORKSPACE_FOLDER: + const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null; + if (workspaceFolder) { + return this.reloadWorkspaceFolderConfiguration(this.workspace.getFolder(overrides.resource), key); + } + } + return null; + }); } - private triggerConfigurationChange(): void { - if (this.getWorkbenchState() === WorkbenchState.EMPTY) { - this._onDidUpdateConfiguration.fire({ source: ConfigurationSource.User, sourceConfig: this._configuration.user.contents }); - } else { - this._onDidUpdateConfiguration.fire({ source: ConfigurationSource.Workspace, sourceConfig: this.workspace.folders.length ? this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri).contents : void 0 }); // TODO@Sandeep debt? + private deriveConfigurationTarget(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): ConfigurationTarget { + if (target) { + return target; } + + if (value === void 0) { + // Ignore. But expected is to remove the value from all targets + return void 0; + } + + const inspect = this.inspect(key, overrides); + if (equals(value, inspect.value)) { + // No change. So ignore. + return void 0; + } + + if (inspect.workspaceFolder !== void 0) { + return ConfigurationTarget.WORKSPACE_FOLDER; + } + + if (inspect.workspace !== void 0) { + return ConfigurationTarget.WORKSPACE; + } + + return ConfigurationTarget.USER; + } + + private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void { + if (configurationEvent.affectedKeys.length) { + configurationEvent.telemetryData(target, this.getTargetConfiguration(target)); + this._onDidChangeConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace)); + } + } + + private getTargetConfiguration(target: ConfigurationTarget): any { + switch (target) { + case ConfigurationTarget.DEFAULT: + return this._configuration.defaults.contents; + case ConfigurationTarget.USER: + return this._configuration.user.contents; + case ConfigurationTarget.WORKSPACE: + return this._configuration.workspace.contents; + } + return {}; } private pathEquals(path1: string, path2: string): boolean { @@ -611,18 +544,24 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return path1 === path2; } + + private disposeFolderConfiguration(folder: IWorkspaceFolder): void { + const folderConfiguration = this.cachedFolderConfigs.get(folder.uri); + if (folderConfiguration) { + folderConfiguration.dispose(); + } + } } class WorkspaceConfiguration extends Disposable { private _workspaceConfigPath: URI; - private _workspaceConfigurationWatcher: ConfigWatcher>; + private _workspaceConfigurationWatcher: ConfigWatcher; private _workspaceConfigurationWatcherDisposables: IDisposable[] = []; private _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; - load(workspaceConfigPath: URI): TPromise { if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) { return this._reload(); @@ -647,7 +586,7 @@ class WorkspaceConfiguration extends Disposable { }); } - get workspaceConfigurationModel(): WorkspaceConfigurationModel { + get workspaceConfigurationModel(): WorkspaceConfigurationModel { return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModel(); } @@ -661,15 +600,15 @@ class WorkspaceConfiguration extends Disposable { } } -class FolderConfiguration extends Disposable { +class FolderConfiguration extends Disposable { private static RELOAD_CONFIGURATION_DELAY = 50; - private bulkFetchFromWorkspacePromise: TPromise; - private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise> }; + private bulkFetchFromWorkspacePromise: TPromise; + private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise }; private reloadConfigurationScheduler: RunOnceScheduler; - private reloadConfigurationEventEmitter: Emitter> = new Emitter>(); + private reloadConfigurationEventEmitter: Emitter = new Emitter(); constructor(private folder: URI, private configFolderRelativePath: string, private scope: ConfigurationScope) { super(); @@ -678,17 +617,17 @@ class FolderConfiguration extends Disposable { this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY)); } - loadConfiguration(): TPromise> { + loadConfiguration(): TPromise { // Load workspace locals return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => { // Consolidate (support *.json files in the workspace settings folder) - const workspaceSettingsConfig = >workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel(null); - const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => >workspaceConfigFiles[key]); - return new FolderConfigurationModel(workspaceSettingsConfig, otherConfigModels, this.scope); + const workspaceSettingsConfig = workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel(null); + const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => workspaceConfigFiles[key]); + return new FolderConfigurationModel(workspaceSettingsConfig, otherConfigModels, this.scope); }); } - private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel }> { + private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel }> { // once: when invoked for the first time we fetch json files that contribute settings if (!this.bulkFetchFromWorkspacePromise) { this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => { @@ -715,7 +654,7 @@ class FolderConfiguration extends Disposable { return this.bulkFetchFromWorkspacePromise.then(() => TPromise.join(this.workspaceFilePathToConfiguration)); } - public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise> { + public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise { const events = event.changes; let affectedByChanges = false; @@ -773,18 +712,18 @@ class FolderConfiguration extends Disposable { }); } - private createConfigModel(content: IContent): ConfigurationModel { + private createConfigModel(content: IContent): ConfigurationModel { const path = this.toFolderRelativePath(content.resource); if (path === WORKSPACE_CONFIG_DEFAULT_PATH) { - return new FolderSettingsModel(content.value, content.resource.toString()); + return new FolderSettingsModel(content.value, content.resource.toString()); } else { const matches = /\/([^\.]*)*\.json/.exec(path); if (matches && matches[1]) { - return new ScopedConfigurationModel(content.value, content.resource.toString(), matches[1]); + return new ScopedConfigurationModel(content.value, content.resource.toString(), matches[1]); } } - return new CustomConfigurationModel(null); + return new CustomConfigurationModel(null); } private isWorkspaceConfigurationFile(folderRelativePath: string): boolean { @@ -852,80 +791,6 @@ function resolveStat(resource: URI): TPromise { }); } -export class Configuration extends BaseConfiguration { - - constructor(private _baseConfiguration: BaseConfiguration, workspaceConfiguration: ConfigurationModel, protected folders: StrictResourceMap>, workspace: Workspace) { - super(_baseConfiguration.defaults, _baseConfiguration.user, workspaceConfiguration, folders, workspace); - } - - updateBaseConfiguration(baseConfiguration: BaseConfiguration): boolean { - const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace); - - this._baseConfiguration = baseConfiguration; - this._defaults = this._baseConfiguration.defaults; - this._user = this._baseConfiguration.user; - this.merge(); - - return !this.equals(current); - } - - updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel, compare: boolean = true): boolean { - const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace); - - this._workspaceConfiguration = workspaceConfiguration; - this.merge(); - - return compare && !this.equals(current); - } - - updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel, compare: boolean): boolean { - const current = this.getValue(null, { resource }); - - this.folders.set(resource, configuration); - this.mergeFolder(resource); - - return compare && !objects.equals(current, this.getValue(null, { resource })); - } - - deleteFolderConfiguration(folder: URI): boolean { - if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) { - // Do not remove workspace configuration - return false; - } - - const changed = this.folders.get(folder).keys.length > 0; - this.folders.delete(folder); - this._foldersConsolidatedConfigurations.delete(folder); - return changed; - } - - getFolderConfigurationModel(folder: URI): FolderConfigurationModel { - return >this.folders.get(folder); - } - - equals(other: any): boolean { - if (!other || !(other instanceof Configuration)) { - return false; - } - - if (!objects.equals(this.getValue(), other.getValue())) { - return false; - } - - if (this._foldersConsolidatedConfigurations.size !== other._foldersConsolidatedConfigurations.size) { - return false; - } - - for (const resource of this._foldersConsolidatedConfigurations.keys()) { - if (!objects.equals(this.getValue(null, { resource }), other.getValue(null, { resource }))) { - return false; - } - } - - return true; - } -} - interface IExportedConfigurationNode { name: string; description: string; @@ -1019,4 +884,4 @@ function versionStringToNumber(versionStr: string): number { } return parseInt(match[1], 10) * 10000 + parseInt(match[2], 10) * 100 + parseInt(match[3], 10); -} +} \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index a6755918258..781260d8502 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -5,8 +5,13 @@ 'use strict'; import * as assert from 'assert'; -import { FolderConfigurationModel, ScopedConfigurationModel, FolderSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels'; +import { join } from 'vs/base/common/paths'; +import { FolderConfigurationModel, ScopedConfigurationModel, FolderSettingsModel, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import URI from 'vs/base/common/uri'; +import { ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; suite('ConfigurationService - Model', () => { @@ -17,7 +22,7 @@ suite('ConfigurationService - Model', () => { const testObject = new FolderConfigurationModel(settingsConfig, [], ConfigurationScope.WINDOW); - assert.equal(testObject.getContentsFor('task'), undefined); + assert.equal(testObject.getSectionContents('task'), undefined); }); test('Test consolidate (settings and tasks)', () => { @@ -94,4 +99,99 @@ suite('ConfigurationService - Model', () => { assert.deepEqual(new FolderConfigurationModel(settingsConfig, [launchConfig, tasksConfig], ConfigurationScope.WINDOW).contents, expected); assert.deepEqual(new FolderConfigurationModel(settingsConfig, [tasksConfig, launchConfig], ConfigurationScope.WINDOW).contents, expected); }); +}); + +suite('WorkspaceConfigurationChangeEvent', () => { + + test('changeEvent affecting workspace folders', () => { + let configurationChangeEvent = new ConfigurationChangeEvent(); + configurationChangeEvent.change(['window.title']); + configurationChangeEvent.change(['window.zoomLevel'], URI.file('folder1')); + configurationChangeEvent.change(['workbench.editor.enablePreview'], URI.file('folder2')); + configurationChangeEvent.change(['window.restoreFullscreen'], URI.file('folder1')); + configurationChangeEvent.change(['window.restoreWindows'], URI.file('folder2')); + configurationChangeEvent.telemetryData(ConfigurationTarget.WORKSPACE, {}); + + let testObject = new WorkspaceConfigurationChangeEvent(configurationChangeEvent, new Workspace('id', 'name', + [new WorkspaceFolder({ index: 0, name: '1', uri: URI.file('folder1') }), + new WorkspaceFolder({ index: 1, name: '2', uri: URI.file('folder2') }), + new WorkspaceFolder({ index: 2, name: '3', uri: URI.file('folder3') })])); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + assert.equal(testObject.source, ConfigurationTarget.WORKSPACE); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('folder1'))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder3', 'file3')))); + + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder3', 'file3')))); + + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder3', 'file3')))); + + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder3'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file('file3'))); + + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', URI.file('folder1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('window', URI.file('folder3'))); + assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); + assert.ok(testObject.affectsConfiguration('window', URI.file('file3'))); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder3'))); + + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder3'))); + + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', URI.file('folder2'))); + assert.ok(testObject.affectsConfiguration('workbench', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder3'))); + + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', URI.file('folder1'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('files', URI.file('folder2'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('files', URI.file('folder3'))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder3', 'file3')))); + }); + }); \ No newline at end of file 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 268e832f7db..bc24a60429e 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -21,13 +21,12 @@ import extfs = require('vs/base/node/extfs'); import { TestTextFileService, TestEditorGroupService, TestLifecycleService, TestBackupFileService, TestTextResourceConfigurationService } from 'vs/workbench/test/workbenchTestServices'; import uuid = require('vs/base/common/uuid'); import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { FileService } from 'vs/workbench/services/files/node/fileService'; -import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; -import { ConfigurationTarget, ConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/node/configurationEditingService'; import { IFileService } from 'vs/platform/files/common/files'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -224,17 +223,19 @@ suite('ConfigurationEditingService', () => { test('write one setting - empty file', () => { return testObject.writeConfiguration(ConfigurationTarget.USER, { key: 'configurationEditing.service.testSetting', value: 'value' }) + .then(() => instantiationService.get(IConfigurationService).reloadConfiguration()) .then(() => { const contents = fs.readFileSync(globalSettingsFile).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['configurationEditing.service.testSetting'], 'value'); - assert.equal(instantiationService.get(IConfigurationService).lookup('configurationEditing.service.testSetting').value, 'value'); + assert.equal(instantiationService.get(IConfigurationService).getValue('configurationEditing.service.testSetting'), 'value'); }); }); test('write one setting - existing file', () => { fs.writeFileSync(globalSettingsFile, '{ "my.super.setting": "my.super.value" }'); return testObject.writeConfiguration(ConfigurationTarget.USER, { key: 'configurationEditing.service.testSetting', value: 'value' }) + .then(() => instantiationService.get(IConfigurationService).reloadConfiguration()) .then(() => { const contents = fs.readFileSync(globalSettingsFile).toString('utf8'); const parsed = json.parse(contents); @@ -242,20 +243,21 @@ suite('ConfigurationEditingService', () => { assert.equal(parsed['my.super.setting'], 'my.super.value'); const configurationService = instantiationService.get(IConfigurationService); - assert.equal(configurationService.lookup('configurationEditing.service.testSetting').value, 'value'); - assert.equal(configurationService.lookup('my.super.setting').value, 'my.super.value'); + assert.equal(configurationService.getValue('configurationEditing.service.testSetting'), 'value'); + assert.equal(configurationService.getValue('my.super.setting'), 'my.super.value'); }); }); test('write workspace standalone setting - empty file', () => { return testObject.writeConfiguration(ConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' }) + .then(() => instantiationService.get(IConfigurationService).reloadConfiguration()) .then(() => { const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['service.testSetting'], 'value'); const configurationService = instantiationService.get(IConfigurationService); - assert.equal(configurationService.lookup('tasks.service.testSetting').value, 'value'); + assert.equal(configurationService.getValue('tasks.service.testSetting'), 'value'); }); }); @@ -263,6 +265,7 @@ suite('ConfigurationEditingService', () => { const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['launch']); fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }'); return testObject.writeConfiguration(ConfigurationTarget.WORKSPACE, { key: 'launch.service.testSetting', value: 'value' }) + .then(() => instantiationService.get(IConfigurationService).reloadConfiguration()) .then(() => { const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); @@ -270,8 +273,8 @@ suite('ConfigurationEditingService', () => { assert.equal(parsed['my.super.setting'], 'my.super.value'); const configurationService = instantiationService.get(IConfigurationService); - assert.equal(configurationService.lookup('launch.service.testSetting').value, 'value'); - assert.equal(configurationService.lookup('launch.my.super.setting').value, 'my.super.value'); + assert.equal(configurationService.getValue('launch.service.testSetting'), 'value'); + assert.equal(configurationService.getValue('launch.my.super.setting'), 'my.super.value'); }); }); diff --git a/src/vs/workbench/services/configuration/test/node/configuration.test.ts b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts similarity index 89% rename from src/vs/workbench/services/configuration/test/node/configuration.test.ts rename to src/vs/workbench/services/configuration/test/node/configurationService.test.ts index ea5d59653ca..211e1f66128 100644 --- a/src/vs/workbench/services/configuration/test/node/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts @@ -19,7 +19,7 @@ import { parseArgs } from 'vs/platform/environment/node/argv'; import extfs = require('vs/base/node/extfs'); import uuid = require('vs/base/common/uuid'); import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -196,7 +196,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.tabs": true }'); return service.initialize(workspaceDir).then(() => { - service.onDidUpdateConfiguration(event => { + service.onDidChangeConfiguration(event => { const config = service.getConfiguration<{ testworkbench: { editor: { tabs: boolean } } }>(); assert.equal(config.testworkbench.editor.tabs, false); @@ -208,7 +208,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.tabs": false }'); // this has to trigger the event since the config changes - service.reloadConfiguration().done(); + service.reloadUserConfiguration().done(); }); }); @@ -228,7 +228,7 @@ suite('WorkspaceConfigurationService - Node', () => { return createService(workspaceDir, globalSettingsFile).then(service => { fs.writeFileSync(globalSettingsFile, '{ "workspace.service.testSetting": "isChanged" }'); - service.reloadConfiguration().then(() => { + service.reloadUserConfiguration().then(() => { const config = service.getConfiguration(); assert.equal(config.workspace.service.testSetting, 'isChanged'); @@ -245,7 +245,7 @@ suite('WorkspaceConfigurationService - Node', () => { return createService(workspaceDir, globalSettingsFile).then(service => { fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "testworkbench.editor.icons": true }'); - service.reloadConfiguration().then(() => { + service.reloadWorkspaceConfiguration().then(() => { const config = service.getConfiguration<{ testworkbench: { editor: { icons: boolean } } }>(); assert.equal(config.testworkbench.editor.icons, true); @@ -279,7 +279,7 @@ suite('WorkspaceConfigurationService - Node', () => { test('workspace change triggers event', (done: () => void) => { createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { return createService(workspaceDir, globalSettingsFile).then(service => { - service.onDidUpdateConfiguration(event => { + service.onDidChangeConfiguration(event => { const config = service.getConfiguration<{ testworkbench: { editor: { icons: boolean } } }>(); assert.equal(config.testworkbench.editor.icons, true); assert.equal(service.getConfiguration().testworkbench.editor.icons, true); @@ -305,11 +305,11 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(settingsFile, '{ "testworkbench.editor.icons": true }'); const target = sinon.stub(); - service.onDidUpdateConfiguration(event => target()); + service.onDidChangeConfiguration(event => target()); fs.writeFileSync(settingsFile, '{ "testworkbench.editor.icons": false }'); - service.reloadConfiguration().done(() => { + service.reloadWorkspaceConfiguration().done(() => { assert.ok(target.calledOnce); service.dispose(); @@ -325,11 +325,11 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "testworkbench.editor.icons": true }'); - service.reloadConfiguration().done(() => { + service.reloadWorkspaceConfiguration().done(() => { const target = sinon.stub(); - service.onDidUpdateConfiguration(event => target()); + service.onDidChangeConfiguration(event => target()); - service.reloadConfiguration().done(() => { + service.reloadWorkspaceConfiguration().done(() => { assert.ok(!target.called); service.dispose(); @@ -344,8 +344,8 @@ suite('WorkspaceConfigurationService - Node', () => { createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { return createService(workspaceDir, globalSettingsFile).then(service => { const target = sinon.stub(); - service.onDidUpdateConfiguration(event => target()); - service.reloadConfiguration().done(() => { + service.onDidChangeConfiguration(event => target()); + service.reloadUserConfiguration().done(() => { assert.ok(!target.called); service.dispose(); cleanUp(done); @@ -358,13 +358,13 @@ suite('WorkspaceConfigurationService - Node', () => { test('lookup', (done: () => void) => { createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { return createService(workspaceDir, globalSettingsFile).then(service => { - let res = service.lookup('something.missing'); + let res = service.inspect('something.missing'); assert.ok(!res.default); assert.ok(!res.user); assert.ok(!res.workspace); assert.ok(!res.value); - res = service.lookup('workspaceLookup.service.testSetting'); + res = service.inspect('workspaceLookup.service.testSetting'); assert.equal(res.default, 'isSet'); assert.equal(res.value, 'isSet'); assert.ok(!res.user); @@ -372,8 +372,8 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "workspaceLookup.service.testSetting": true }'); - return service.reloadConfiguration().then(() => { - res = service.lookup('workspaceLookup.service.testSetting'); + return service.reloadUserConfiguration().then(() => { + res = service.inspect('workspaceLookup.service.testSetting'); assert.equal(res.default, 'isSet'); assert.equal(res.user, true); assert.equal(res.value, true); @@ -382,8 +382,8 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.testSetting": 55 }'); - return service.reloadConfiguration().then(() => { - res = service.lookup('workspaceLookup.service.testSetting'); + return service.reloadWorkspaceConfiguration().then(() => { + res = service.inspect('workspaceLookup.service.testSetting'); assert.equal(res.default, 'isSet'); assert.equal(res.user, true); assert.equal(res.workspace, 55); @@ -420,7 +420,7 @@ suite('WorkspaceConfigurationService - Node', () => { fs.writeFileSync(globalSettingsFile, '{ "workspaceLookup.service.testSetting": true }'); - return service.reloadConfiguration().then(() => { + return service.reloadUserConfiguration().then(() => { keys = service.keys(); assert.ok(contains(keys.default, 'workspaceLookup.service.testSetting')); @@ -430,7 +430,7 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.testSetting": 55 }'); - return service.reloadConfiguration().then(() => { + return service.reloadWorkspaceConfiguration().then(() => { keys = service.keys(); assert.ok(contains(keys.default, 'workspaceLookup.service.testSetting')); @@ -440,7 +440,7 @@ suite('WorkspaceConfigurationService - Node', () => { const settingsFile = path.join(workspaceDir, '.vscode', 'tasks.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.taskTestSetting": 55 }'); - return service.reloadConfiguration().then(() => { + return service.reloadWorkspaceConfiguration().then(() => { keys = service.keys(); assert.ok(!contains(keys.default, 'tasks.workspaceLookup.service.taskTestSetting')); @@ -460,31 +460,31 @@ suite('WorkspaceConfigurationService - Node', () => { test('values', (done: () => void) => { createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { return createService(workspaceDir, globalSettingsFile).then(service => { - let values = service.values(); - let value = values['workspaceLookup.service.testSetting']; + let values = service.inspect('workspaceLookup.service.testSetting'); + let value = values.value; assert.ok(value); - assert.equal(value.default, 'isSet'); + assert.equal(values.default, 'isSet'); fs.writeFileSync(globalSettingsFile, '{ "workspaceLookup.service.testSetting": true }'); - return service.reloadConfiguration().then(() => { - values = service.values(); - value = values['workspaceLookup.service.testSetting']; + return service.reloadUserConfiguration().then(() => { + values = service.inspect('workspaceLookup.service.testSetting'); + value = values.value; assert.ok(value); - assert.equal(value.user, true); + assert.equal(values.user, true); const settingsFile = path.join(workspaceDir, '.vscode', 'settings.json'); fs.writeFileSync(settingsFile, '{ "workspaceLookup.service.testSetting": 55 }'); - return service.reloadConfiguration().then(() => { - values = service.values(); - value = values['workspaceLookup.service.testSetting']; + return service.reloadWorkspaceConfiguration().then(() => { + values = service.inspect('workspaceLookup.service.testSetting'); + value = values.value; assert.ok(value); - assert.equal(value.user, true); - assert.equal(value.workspace, 55); + assert.equal(values.user, true); + assert.equal(values.workspace, 55); done(); }); diff --git a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts index 7841abf9606..fce774139c1 100644 --- a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts @@ -7,7 +7,7 @@ import assert = require('assert'); import uri from 'vs/base/common/uri'; import platform = require('vs/base/common/platform'); import { TPromise } from 'vs/base/common/winjs.base'; -import { IConfigurationService, getConfigurationValue, IConfigurationOverrides, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, getConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/node/configurationResolverService'; @@ -348,13 +348,14 @@ class MockConfigurationService implements IConfigurationService { public _serviceBrand: any; public serviceId = IConfigurationService; public constructor(private configuration: any = {}) { } - public reloadConfiguration(section?: string): TPromise { return TPromise.as(this.getConfiguration()); } - public lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspace: void 0, folder: void 0 }; } - public keys() { return { default: [], user: [], workspace: [], folder: [] }; } - public values() { return {}; } + public inspect(key: string, overrides?: IConfigurationOverrides): any { return { value: getConfigurationValue(this.getConfiguration(), key), default: getConfigurationValue(this.getConfiguration(), key), user: getConfigurationValue(this.getConfiguration(), key), workspaceFolder: void 0, folder: void 0 }; } + public keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; } public getConfiguration(): any { return this.configuration; } + public getValue(key: string): any { return getConfigurationValue(this.getConfiguration(), key); } + public updateValue(): TPromise { return null; } public getConfigurationData(): any { return null; } - public onDidUpdateConfiguration() { return { dispose() { } }; } + public onDidChangeConfiguration() { return { dispose() { } }; } + public reloadConfiguration() { return null; } } class MockCommandService implements ICommandService { diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts new file mode 100644 index 00000000000..6356737cb2d --- /dev/null +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import URI from 'vs/base/common/uri'; +import Event from 'vs/base/common/event'; +import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export const IDecorationsService = createDecorator('IFileDecorationsService'); + +export interface IDecorationData { + readonly weight?: number; + readonly color?: ColorIdentifier; + readonly letter?: string; + readonly title?: string; + readonly bubble?: boolean; +} + +export interface IDecoration { + readonly _decoBrand: undefined; + readonly weight?: number; + readonly title?: string; + readonly labelClassName?: string; + readonly badgeClassName?: string; +} + +export interface IDecorationsProvider { + readonly label: string; + readonly onDidChange: Event; + provideDecorations(uri: URI): IDecorationData | Thenable; +} + +export interface IResourceDecorationChangeEvent { + affectsResource(uri: URI): boolean; +} + +export interface IDecorationsService { + + readonly _serviceBrand: any; + + readonly onDidChangeDecorations: Event; + + registerDecorationsProvider(provider: IDecorationsProvider): IDisposable; + + getDecoration(uri: URI, includeChildren: boolean): IDecoration; +} diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts new file mode 100644 index 00000000000..cd004e68f20 --- /dev/null +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -0,0 +1,402 @@ +/*--------------------------------------------------------------------------------------------- + * 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 URI from 'vs/base/common/uri'; +import Event, { Emitter, debounceEvent, any } from 'vs/base/common/event'; +import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { isThenable } from 'vs/base/common/async'; +import { LinkedList } from 'vs/base/common/linkedList'; +import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; +import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IdGenerator } from 'vs/base/common/idGenerator'; +import { listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IIterator } from 'vs/base/common/iterator'; + +class DecorationRule { + + static keyOf(data: IDecorationData | IDecorationData[]): string { + if (Array.isArray(data)) { + return data.map(DecorationRule.keyOf).join(','); + } else { + const { color, letter } = data; + return `${color}/${letter}`; + } + } + + private static readonly _classNames = new IdGenerator('monaco-decorations-style-'); + + readonly data: IDecorationData | IDecorationData[]; + readonly labelClassName: string; + readonly badgeClassName: string; + + constructor(data: IDecorationData | IDecorationData[]) { + this.data = data; + this.labelClassName = DecorationRule._classNames.nextId(); + this.badgeClassName = DecorationRule._classNames.nextId(); + } + + appendCSSRules(element: HTMLStyleElement, theme: ITheme): void { + if (!Array.isArray(this.data)) { + this._appendForOne(this.data, element, theme); + } else { + this._appendForMany(this.data, element, theme); + } + } + + private _appendForOne(data: IDecorationData, element: HTMLStyleElement, theme: ITheme): void { + const { color, letter } = data; + // label + createCSSRule(`.${this.labelClassName}`, `color: ${theme.getColor(color) || 'inherit'};`, element); + createCSSRule(`.focused .selected .${this.labelClassName}`, `color: inherit; opacity: inherit;`, element); + // badge + if (letter) { + createCSSRule(`.${this.badgeClassName}`, `background-color: ${theme.getColor(color)}; color: ${theme.getColor(listActiveSelectionForeground)};`, element); + createCSSRule(`.${this.badgeClassName}::before`, `content: "${letter}"`, element); + } + } + + private _appendForMany(data: IDecorationData[], element: HTMLStyleElement, theme: ITheme): void { + // label + const { color } = data[0]; + createCSSRule(`.${this.labelClassName}`, `color: ${theme.getColor(color) || 'inherit'};`, element); + createCSSRule(`.focused .selected .${this.labelClassName}`, `color: inherit; opacity: inherit;`, element); + + // badge + let letters: string[] = []; + let colors: string[] = []; + for (const deco of data) { + letters.push(deco.letter); + colors.push(`${theme.getColor(deco.color).toString()} ${100 / data.length}%`); + } + createCSSRule(`.${this.badgeClassName}::before`, `content: "${letters.join('\u2002')}"`, element); + createCSSRule( + `.${this.badgeClassName}`, + `background: linear-gradient(90deg, ${colors.join()}); color: ${theme.getColor(listActiveSelectionForeground)};`, + element + ); + } + + removeCSSRules(element: HTMLStyleElement): void { + removeCSSRulesContainingSelector(this.labelClassName, element); + removeCSSRulesContainingSelector(this.badgeClassName, element); + } +} + +class ResourceDecoration implements IDecoration { + + static from(data: IDecorationData | IDecorationData[]): ResourceDecoration { + let result = new ResourceDecoration(data); + if (Array.isArray(data)) { + result.weight = data[0].weight; + result.title = data.map(d => d.title).join(', '); + } else { + result.weight = data.weight; + result.title = data.title; + } + return result; + } + + _decoBrand: undefined; + _data: IDecorationData | IDecorationData[]; + + weight?: number; + title?: string; + labelClassName?: string; + badgeClassName?: string; + + private constructor(data: IDecorationData | IDecorationData[]) { + this._data = data; + } +} + +class DecorationStyles { + + private readonly _disposables: IDisposable[]; + private readonly _styleElement = createStyleSheet(); + private readonly _decorationRules = new Map(); + + constructor( + private _themeService: IThemeService, + ) { + this._disposables = [ + this._themeService.onThemeChange(this._onThemeChange, this), + ]; + } + + dispose(): void { + dispose(this._disposables); + this._styleElement.parentElement.removeChild(this._styleElement); + } + + asDecoration(data: IDecorationData | IDecorationData[]): ResourceDecoration { + let key = DecorationRule.keyOf(data); + let rule = this._decorationRules.get(key); + let result = ResourceDecoration.from(data); + + if (!rule) { + // new css rule + rule = new DecorationRule(data); + this._decorationRules.set(key, rule); + rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); + } + + result.labelClassName = rule.labelClassName; + result.badgeClassName = rule.badgeClassName; + return result; + } + + private _onThemeChange(): void { + this._decorationRules.forEach(rule => { + rule.removeCSSRules(this._styleElement); + rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); + }); + } + + cleanUp(iter: IIterator): void { + // remove every rule for which no more + // decoration (data) is kept. this isn't cheap + let usedDecorations = new Set(); + for (let e = iter.next(); !e.done; e = iter.next()) { + e.value.data.forEach(value => { + if (value instanceof ResourceDecoration) { + if (Array.isArray(value._data)) { + value._data.forEach(data => usedDecorations.add(data)); + } else { + usedDecorations.add(value._data); + } + } + }); + } + this._decorationRules.forEach((value, index) => { + const { data } = value; + let remove: boolean; + if (Array.isArray(data)) { + remove = data.every(data => !usedDecorations.has(data)); + } else if (!usedDecorations.has(data)) { + remove = true; + } + if (remove) { + value.removeCSSRules(this._styleElement); + this._decorationRules.delete(index); + } + }); + } +} + +class FileDecorationChangeEvent implements IResourceDecorationChangeEvent { + + private readonly _data = TernarySearchTree.forPaths(); + + affectsResource(uri: URI): boolean { + return this._data.get(uri.toString()) || this._data.findSuperstr(uri.toString()) !== undefined; + } + + static debouncer(last: FileDecorationChangeEvent, current: URI | URI[]) { + if (!last) { + last = new FileDecorationChangeEvent(); + } + if (Array.isArray(current)) { + // many + for (const uri of current) { + last._data.set(uri.toString(), true); + } + } else { + // one + last._data.set(current.toString(), true); + } + + return last; + } +} + +class DecorationProviderWrapper { + + readonly data = TernarySearchTree.forPaths | IDecorationData>(); + private readonly _dispoable: IDisposable; + + constructor( + private readonly _provider: IDecorationsProvider, + private readonly _uriEmitter: Emitter, + private readonly _flushEmitter: Emitter + ) { + this._dispoable = this._provider.onDidChange(uris => { + if (!uris) { + // flush event -> drop all data, can affect everything + this.data.clear(); + this._flushEmitter.fire({ affectsResource() { return true; } }); + + } else { + // selective changes -> drop for resource, fetch again, send event + for (const uri of uris) { + this.data.delete(uri.toString()); + this._fetchData(uri); + } + } + }); + } + + dispose(): void { + this._dispoable.dispose(); + this.data.clear(); + } + + knowsAbout(uri: URI): boolean { + return Boolean(this.data.get(uri.toString())) || Boolean(this.data.findSuperstr(uri.toString())); + } + + getOrRetrieve(uri: URI, includeChildren: boolean, callback: (data: IDecorationData, isChild: boolean) => void): void { + const key = uri.toString(); + let item = this.data.get(key); + + if (isThenable(item)) { + // pending -> still waiting + return; + } + + if (item === undefined) { + // unknown -> trigger request + item = this._fetchData(uri); + } + + if (item) { + // found something + callback(item, false); + } + + if (includeChildren) { + // (resolved) children + const childTree = this.data.findSuperstr(key); + if (childTree) { + childTree.forEach(value => { + if (value && !isThenable(value)) { + callback(value, true); + } + }); + } + } + } + + private _fetchData(uri: URI): IDecorationData { + + const dataOrThenable = this._provider.provideDecorations(uri); + if (!isThenable(dataOrThenable)) { + // sync -> we have a result now + return this._keepItem(uri, dataOrThenable); + + } else { + // async -> we have a result soon + const request = Promise.resolve(dataOrThenable) + .then(data => this._keepItem(uri, data)) + .catch(_ => this.data.delete(uri.toString())); + + this.data.set(uri.toString(), request); + return undefined; + } + } + + private _keepItem(uri: URI, data: IDecorationData): IDecorationData { + let deco = data ? data : null; + this.data.set(uri.toString(), deco); + this._uriEmitter.fire(uri); + return deco; + } +} + +export class FileDecorationsService implements IDecorationsService { + + _serviceBrand: any; + + private readonly _data = new LinkedList(); + private readonly _onDidChangeDecorationsDelayed = new Emitter(); + private readonly _onDidChangeDecorations = new Emitter(); + private readonly _decorationStyles: DecorationStyles; + private readonly _disposables: IDisposable[]; + + readonly onDidChangeDecorations: Event = any( + this._onDidChangeDecorations.event, + debounceEvent( + this._onDidChangeDecorationsDelayed.event, + FileDecorationChangeEvent.debouncer + ) + ); + + constructor( + @IThemeService themeService: IThemeService, + cleanUpCount: number = 17 + ) { + this._decorationStyles = new DecorationStyles(themeService); + + // every so many events we check if there are + // css styles that we don't need anymore + let count = 0; + let reg = this.onDidChangeDecorations(() => { + if (++count % cleanUpCount === 0) { + this._decorationStyles.cleanUp(this._data.iterator()); + } + }); + + this._disposables = [ + reg, + this._decorationStyles + ]; + } + + dispose(): void { + dispose(this._disposables); + } + + registerDecorationsProvider(provider: IDecorationsProvider): IDisposable { + + const wrapper = new DecorationProviderWrapper( + provider, + this._onDidChangeDecorationsDelayed, + this._onDidChangeDecorations + ); + const remove = this._data.push(wrapper); + + this._onDidChangeDecorations.fire({ + // everything might have changed + affectsResource() { return true; } + }); + + return { + dispose: () => { + // fire event that says 'yes' for any resource + // known to this provider. then dispose and remove it. + remove(); + this._onDidChangeDecorations.fire({ affectsResource: uri => wrapper.knowsAbout(uri) }); + wrapper.dispose(); + } + }; + } + + getDecoration(uri: URI, includeChildren: boolean): IDecoration { + let data: IDecorationData[] = []; + let onlyChildren = true; + for (let iter = this._data.iterator(), next = iter.next(); !next.done; next = iter.next()) { + next.value.getOrRetrieve(uri, includeChildren, (deco, isChild) => { + if (!isChild || deco.bubble) { + data.push(deco); + onlyChildren = onlyChildren && isChild; + } + }); + } + + if (data.length === 0) { + return undefined; + } else if (onlyChildren) { + let result = this._decorationStyles.asDecoration(data.sort((a, b) => b.weight - a.weight)[0]); + result.badgeClassName = ''; + return result; + } else if (data.length === 1) { + return this._decorationStyles.asDecoration(data[0]); + } else { + return this._decorationStyles.asDecoration(data.sort((a, b) => b.weight - a.weight)); + } + } +} diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts new file mode 100644 index 00000000000..721051fb31a --- /dev/null +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { FileDecorationsService } from 'vs/workbench/services/decorations/browser/decorationsService'; +import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; +import URI from 'vs/base/common/uri'; +import Event, { toPromise } from 'vs/base/common/event'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; + +suite('DecorationsService', function () { + + let service: FileDecorationsService; + + setup(function () { + if (service) { + service.dispose(); + } + service = new FileDecorationsService(new TestThemeService()); + }); + + test('Async provider, async/evented result', function () { + + let uri = URI.parse('foo:bar'); + let callCounter = 0; + + service.registerDecorationsProvider(new class implements IDecorationsProvider { + readonly label: string = 'Test'; + readonly onDidChange: Event = Event.None; + provideDecorations(uri: URI) { + callCounter += 1; + return new Promise(resolve => { + setTimeout(() => resolve({ + color: 'someBlue', + title: 'T' + })); + }); + } + }); + + // trigger -> async + assert.equal(service.getDecoration(uri, false), undefined); + assert.equal(callCounter, 1); + + // event when result is computed + return toPromise(service.onDidChangeDecorations).then(e => { + assert.equal(e.affectsResource(uri), true); + + // sync result + assert.deepEqual(service.getDecoration(uri, false).title, 'T'); + assert.equal(callCounter, 1); + }); + }); + + test('Sync provider, sync result', function () { + + let uri = URI.parse('foo:bar'); + let callCounter = 0; + + service.registerDecorationsProvider(new class implements IDecorationsProvider { + readonly label: string = 'Test'; + readonly onDidChange: Event = Event.None; + provideDecorations(uri: URI) { + callCounter += 1; + return { color: 'someBlue', title: 'Z' }; + } + }); + + // trigger -> sync + assert.deepEqual(service.getDecoration(uri, false).title, 'Z'); + assert.equal(callCounter, 1); + }); + + test('Clear decorations on provider dispose', function () { + let uri = URI.parse('foo:bar'); + let callCounter = 0; + + let reg = service.registerDecorationsProvider(new class implements IDecorationsProvider { + readonly label: string = 'Test'; + readonly onDidChange: Event = Event.None; + provideDecorations(uri: URI) { + callCounter += 1; + return { color: 'someBlue', title: 'J' }; + } + }); + + // trigger -> sync + assert.deepEqual(service.getDecoration(uri, false).title, 'J'); + assert.equal(callCounter, 1); + + // un-register -> ensure good event + let didSeeEvent = false; + service.onDidChangeDecorations(e => { + assert.equal(e.affectsResource(uri), true); + assert.deepEqual(service.getDecoration(uri, false), undefined); + assert.equal(callCounter, 1); + didSeeEvent = true; + }); + reg.dispose(); + assert.equal(didSeeEvent, true); + }); + + test('No default bubbling', function () { + + let reg = service.registerDecorationsProvider({ + label: 'Test', + onDidChange: Event.None, + provideDecorations(uri: URI) { + return uri.path.match(/\.txt/) + ? { title: '.txt' } + : undefined; + } + }); + + let childUri = URI.parse('file:///some/path/some/file.txt'); + + let deco = service.getDecoration(childUri, false); + assert.equal(deco.title, '.txt'); + + deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true); + assert.equal(deco, undefined); + reg.dispose(); + + // bubble + reg = service.registerDecorationsProvider({ + label: 'Test', + onDidChange: Event.None, + provideDecorations(uri: URI) { + return uri.path.match(/\.txt/) + ? { title: '.txt.bubble', bubble: true } + : undefined; + } + }); + + deco = service.getDecoration(childUri, false); + assert.equal(deco.title, '.txt.bubble'); + + deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true); + assert.equal(deco.title, '.txt.bubble'); + }); +}); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts deleted file mode 100644 index df0ec9e78cb..00000000000 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ /dev/null @@ -1,366 +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 { TPromise } from 'vs/base/common/winjs.base'; -import URI from 'vs/base/common/uri'; -import network = require('vs/base/common/network'); -import { Registry } from 'vs/platform/registry/common/platform'; -import { basename, dirname } from 'vs/base/common/paths'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, TextEditorOptions, Extensions as EditorExtensions, SideBySideEditorInput, IFileEditorInput, IFileInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { IUntitledEditorService, UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IWorkbenchEditorService, IResourceInputType } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorInput, IEditorOptions, ITextEditorOptions, Position, Direction, IEditor, IResourceInput, IResourceDiffInput, IResourceSideBySideInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import nls = require('vs/nls'); -import { getPathLabel } from 'vs/base/common/labels'; -import { ResourceMap } from 'vs/base/common/map'; -import { once } from 'vs/base/common/event'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; - -export interface IEditorPart { - openEditor(input?: IEditorInput, options?: IEditorOptions | ITextEditorOptions, sideBySide?: boolean): TPromise; - openEditor(input?: IEditorInput, options?: IEditorOptions | ITextEditorOptions, position?: Position): TPromise; - openEditors(editors: { input: IEditorInput, position: Position, options?: IEditorOptions | ITextEditorOptions }[]): TPromise; - replaceEditors(editors: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: IEditorOptions | ITextEditorOptions }[], position?: Position): TPromise; - closeEditor(position: Position, input: IEditorInput): TPromise; - closeEditors(position: Position, filter?: { except?: IEditorInput, direction?: Direction, unmodifiedOnly?: boolean }): TPromise; - closeAllEditors(except?: Position): TPromise; - getActiveEditor(): BaseEditor; - getVisibleEditors(): IEditor[]; - getActiveEditorInput(): IEditorInput; -} - -type ICachedEditorInput = ResourceEditorInput | IFileEditorInput; - -export class WorkbenchEditorService implements IWorkbenchEditorService { - - public _serviceBrand: any; - - private static CACHE: ResourceMap = new ResourceMap(); - - private editorPart: IEditorPart | IWorkbenchEditorService; - private fileInputFactory: IFileInputFactory; - - constructor( - editorPart: IEditorPart | IWorkbenchEditorService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, - @IInstantiationService private instantiationService: IInstantiationService, - @IEnvironmentService private environmentService: IEnvironmentService, - @IFileService private fileService: IFileService - ) { - this.editorPart = editorPart; - this.fileInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileInputFactory(); - } - - public getActiveEditor(): IEditor { - return this.editorPart.getActiveEditor(); - } - - public getActiveEditorInput(): IEditorInput { - return this.editorPart.getActiveEditorInput(); - } - - public getVisibleEditors(): IEditor[] { - return this.editorPart.getVisibleEditors(); - } - - public isVisible(input: IEditorInput, includeSideBySide: boolean): boolean { - if (!input) { - return false; - } - - return this.getVisibleEditors().some(editor => { - if (!editor.input) { - return false; - } - - if (input.matches(editor.input)) { - return true; - } - - if (includeSideBySide && editor.input instanceof SideBySideEditorInput) { - const sideBySideInput = editor.input; - return input.matches(sideBySideInput.master) || input.matches(sideBySideInput.details); - } - - return false; - }); - } - - public openEditor(input: IEditorInput, options?: IEditorOptions, sideBySide?: boolean): TPromise; - public openEditor(input: IEditorInput, options?: IEditorOptions, position?: Position): TPromise; - public openEditor(input: IResourceInputType, position?: Position): TPromise; - public openEditor(input: IResourceInputType, sideBySide?: boolean): TPromise; - public openEditor(input: any, arg2?: any, arg3?: any): TPromise { - if (!input) { - return TPromise.as(null); - } - - // Workbench Input Support - if (input instanceof EditorInput) { - return this.doOpenEditor(input, this.toOptions(arg2), arg3); - } - - // Support opening foreign resources (such as a http link that points outside of the workbench) - const resourceInput = input; - if (resourceInput.resource instanceof URI) { - const schema = resourceInput.resource.scheme; - if (schema === network.Schemas.http || schema === network.Schemas.https) { - window.open(resourceInput.resource.toString(true)); - - return TPromise.as(null); - } - } - - // Untyped Text Editor Support (required for code that uses this service below workbench level) - const textInput = input; - const typedInput = this.createInput(textInput); - if (typedInput) { - return this.doOpenEditor(typedInput, TextEditorOptions.from(textInput), arg2); - } - - return TPromise.as(null); - } - - private toOptions(options?: IEditorOptions | EditorOptions): EditorOptions { - if (!options || options instanceof EditorOptions) { - return options as EditorOptions; - } - - const textOptions: ITextEditorOptions = options; - if (!!textOptions.selection) { - return TextEditorOptions.create(options); - } - - return EditorOptions.create(options); - } - - /** - * Allow subclasses to implement their own behavior for opening editor (see below). - */ - protected doOpenEditor(input: IEditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; - protected doOpenEditor(input: IEditorInput, options?: EditorOptions, position?: Position): TPromise; - protected doOpenEditor(input: IEditorInput, options?: EditorOptions, arg3?: any): TPromise { - return this.editorPart.openEditor(input, options, arg3); - } - - public openEditors(editors: { input: IResourceInputType, position: Position }[]): TPromise; - public openEditors(editors: { input: IEditorInput, position: Position, options?: IEditorOptions }[]): TPromise; - public openEditors(editors: any[]): TPromise { - const inputs = editors.map(editor => this.createInput(editor.input)); - const typedInputs: { input: IEditorInput, position: Position, options?: EditorOptions }[] = inputs.map((input, index) => { - const options = editors[index].input instanceof EditorInput ? this.toOptions(editors[index].options) : TextEditorOptions.from(editors[index].input); - - return { - input, - options, - position: editors[index].position - }; - }); - - return this.editorPart.openEditors(typedInputs); - } - - public replaceEditors(editors: { toReplace: IResourceInputType, replaceWith: IResourceInputType }[], position?: Position): TPromise; - public replaceEditors(editors: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: IEditorOptions }[], position?: Position): TPromise; - public replaceEditors(editors: any[], position?: Position): TPromise { - const toReplaceInputs = editors.map(editor => this.createInput(editor.toReplace)); - const replaceWithInputs = editors.map(editor => this.createInput(editor.replaceWith)); - const typedReplacements: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: EditorOptions }[] = editors.map((editor, index) => { - const options = editor.toReplace instanceof EditorInput ? this.toOptions(editor.options) : TextEditorOptions.from(editor.replaceWith); - - return { - toReplace: toReplaceInputs[index], - replaceWith: replaceWithInputs[index], - options - }; - }); - - return this.editorPart.replaceEditors(typedReplacements, position); - } - - public closeEditor(position: Position, input: IEditorInput): TPromise { - return this.doCloseEditor(position, input); - } - - protected doCloseEditor(position: Position, input: IEditorInput): TPromise { - return this.editorPart.closeEditor(position, input); - } - - public closeEditors(position: Position, filter?: { except?: IEditorInput, direction?: Direction, unmodifiedOnly?: boolean }): TPromise { - return this.editorPart.closeEditors(position, filter); - } - - public closeAllEditors(except?: Position): TPromise { - return this.editorPart.closeAllEditors(except); - } - - public createInput(input: IEditorInput): EditorInput; - public createInput(input: IResourceInputType): EditorInput; - public createInput(input: any): IEditorInput { - - // Workbench Input Support - if (input instanceof EditorInput) { - return input; - } - - // Side by Side Support - const resourceSideBySideInput = input; - if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) { - const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource }); - const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource }); - - return new SideBySideEditorInput(resourceSideBySideInput.label || masterInput.getName(), typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(), detailInput, masterInput); - } - - // Diff Editor Support - const resourceDiffInput = input; - if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) { - const leftInput = this.createInput({ resource: resourceDiffInput.leftResource }); - const rightInput = this.createInput({ resource: resourceDiffInput.rightResource }); - const label = resourceDiffInput.label || this.toDiffLabel(resourceDiffInput.leftResource, resourceDiffInput.rightResource, this.workspaceContextService, this.environmentService); - - return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput); - } - - // Untitled file support - const untitledInput = input; - if (!untitledInput.resource || typeof untitledInput.filePath === 'string' || (untitledInput.resource instanceof URI && untitledInput.resource.scheme === UNTITLED_SCHEMA)) { - return this.untitledEditorService.createOrGet(untitledInput.filePath ? URI.file(untitledInput.filePath) : untitledInput.resource, untitledInput.language, untitledInput.contents, untitledInput.encoding); - } - - const resourceInput = input; - - // Files support - if (resourceInput.resource instanceof URI && resourceInput.resource.scheme === network.Schemas.file) { - return this.createOrGet(resourceInput.resource, this.instantiationService, resourceInput.label, resourceInput.description, resourceInput.encoding); - } - - // Any other resource - else if (resourceInput.resource instanceof URI) { - const label = resourceInput.label || basename(resourceInput.resource.fsPath); - let description: string; - if (typeof resourceInput.description === 'string') { - description = resourceInput.description; - } else if (resourceInput.resource.scheme === network.Schemas.file) { - description = dirname(resourceInput.resource.fsPath); - } - - return this.createOrGet(resourceInput.resource, this.instantiationService, label, description); - } - - return null; - } - - private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string, description: string, encoding?: string): ICachedEditorInput { - if (WorkbenchEditorService.CACHE.has(resource)) { - const input = WorkbenchEditorService.CACHE.get(resource); - if (input instanceof ResourceEditorInput) { - input.setName(label); - input.setDescription(description); - } else { - input.setPreferredEncoding(encoding); - } - - return input; - } - - let input: ICachedEditorInput; - if (resource.scheme === network.Schemas.file || this.fileService.canHandleResource && this.fileService.canHandleResource(resource)) { - input = this.fileInputFactory.createFileInput(resource, encoding, instantiationService); - } else { - input = instantiationService.createInstance(ResourceEditorInput, label, description, resource); - } - - WorkbenchEditorService.CACHE.set(resource, input); - once(input.onDispose)(() => { - WorkbenchEditorService.CACHE.delete(resource); - }); - - return input; - } - - private toDiffLabel(res1: URI, res2: URI, context: IWorkspaceContextService, environment: IEnvironmentService): string { - const leftName = getPathLabel(res1.fsPath, context, environment); - const rightName = getPathLabel(res2.fsPath, context, environment); - - return nls.localize('compareLabels', "{0} ↔ {1}", leftName, rightName); - } -} - -export interface IEditorOpenHandler { - (input: IEditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; - (input: IEditorInput, options?: EditorOptions, position?: Position): TPromise; -} - -export interface IEditorCloseHandler { - (position: Position, input: IEditorInput): TPromise; -} - -/** - * Subclass of workbench editor service that delegates all calls to the provided editor service. Subclasses can choose to override the behavior - * of openEditor() and closeEditor() by providing a handler. - * - * This gives clients a chance to override the behavior of openEditor() and closeEditor(). - */ -export class DelegatingWorkbenchEditorService extends WorkbenchEditorService { - private editorOpenHandler: IEditorOpenHandler; - private editorCloseHandler: IEditorCloseHandler; - - constructor( - @IUntitledEditorService untitledEditorService: IUntitledEditorService, - @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IEnvironmentService environmentService: IEnvironmentService, - @IFileService fileService: IFileService - ) { - super( - editorService, - untitledEditorService, - workspaceContextService, - instantiationService, - environmentService, - fileService - ); - } - - public setEditorOpenHandler(handler: IEditorOpenHandler): void { - this.editorOpenHandler = handler; - } - - public setEditorCloseHandler(handler: IEditorCloseHandler): void { - this.editorCloseHandler = handler; - } - - protected doOpenEditor(input: IEditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; - protected doOpenEditor(input: IEditorInput, options?: EditorOptions, position?: Position): TPromise; - protected doOpenEditor(input: IEditorInput, options?: EditorOptions, arg3?: any): TPromise { - const handleOpen = this.editorOpenHandler ? this.editorOpenHandler(input, options, arg3) : TPromise.as(void 0); - - return handleOpen.then(editor => { - if (editor) { - return TPromise.as(editor); - } - - return super.doOpenEditor(input, options, arg3); - }); - } - - protected doCloseEditor(position: Position, input: IEditorInput): TPromise { - const handleClose = this.editorCloseHandler ? this.editorCloseHandler(position, input) : TPromise.as(void 0); - - return handleClose.then(() => { - return super.doCloseEditor(position, input); - }); - } -} diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 05f2ae36cc2..8100a8cd551 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -6,8 +6,23 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService, IEditor, IEditorInput, IEditorOptions, ITextEditorOptions, Position, Direction, IResourceInput, IResourceDiffInput, IResourceSideBySideInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor'; +import URI from 'vs/base/common/uri'; +import network = require('vs/base/common/network'); +import { Registry } from 'vs/platform/registry/common/platform'; +import { basename, dirname } from 'vs/base/common/paths'; +import { EditorInput, EditorOptions, TextEditorOptions, Extensions as EditorExtensions, SideBySideEditorInput, IFileEditorInput, IFileInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; +import { IUntitledEditorService, UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import nls = require('vs/nls'); +import { getPathLabel } from 'vs/base/common/labels'; +import { ResourceMap } from 'vs/base/common/map'; +import { once } from 'vs/base/common/event'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; export const IWorkbenchEditorService = createDecorator('editorService'); @@ -91,4 +106,344 @@ export interface IWorkbenchEditorService extends IEditorService { * Allows to resolve an untyped input to a workbench typed instanceof editor input */ createInput(input: IResourceInputType): IEditorInput; -} \ No newline at end of file +} + +export interface IEditorPart { + openEditor(input?: IEditorInput, options?: IEditorOptions | ITextEditorOptions, sideBySide?: boolean): TPromise; + openEditor(input?: IEditorInput, options?: IEditorOptions | ITextEditorOptions, position?: Position): TPromise; + openEditors(editors: { input: IEditorInput, position: Position, options?: IEditorOptions | ITextEditorOptions }[]): TPromise; + replaceEditors(editors: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: IEditorOptions | ITextEditorOptions }[], position?: Position): TPromise; + closeEditor(position: Position, input: IEditorInput): TPromise; + closeEditors(position: Position, filter?: { except?: IEditorInput, direction?: Direction, unmodifiedOnly?: boolean }): TPromise; + closeAllEditors(except?: Position): TPromise; + getActiveEditor(): IEditor; + getVisibleEditors(): IEditor[]; + getActiveEditorInput(): IEditorInput; +} + +type ICachedEditorInput = ResourceEditorInput | IFileEditorInput; + +export class WorkbenchEditorService implements IWorkbenchEditorService { + + public _serviceBrand: any; + + private static CACHE: ResourceMap = new ResourceMap(); + + private editorPart: IEditorPart | IWorkbenchEditorService; + private fileInputFactory: IFileInputFactory; + + constructor( + editorPart: IEditorPart | IWorkbenchEditorService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IInstantiationService private instantiationService: IInstantiationService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IFileService private fileService: IFileService + ) { + this.editorPart = editorPart; + this.fileInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileInputFactory(); + } + + public getActiveEditor(): IEditor { + return this.editorPart.getActiveEditor(); + } + + public getActiveEditorInput(): IEditorInput { + return this.editorPart.getActiveEditorInput(); + } + + public getVisibleEditors(): IEditor[] { + return this.editorPart.getVisibleEditors(); + } + + public isVisible(input: IEditorInput, includeSideBySide: boolean): boolean { + if (!input) { + return false; + } + + return this.getVisibleEditors().some(editor => { + if (!editor.input) { + return false; + } + + if (input.matches(editor.input)) { + return true; + } + + if (includeSideBySide && editor.input instanceof SideBySideEditorInput) { + const sideBySideInput = editor.input; + return input.matches(sideBySideInput.master) || input.matches(sideBySideInput.details); + } + + return false; + }); + } + + public openEditor(input: IEditorInput, options?: IEditorOptions, sideBySide?: boolean): TPromise; + public openEditor(input: IEditorInput, options?: IEditorOptions, position?: Position): TPromise; + public openEditor(input: IResourceInputType, position?: Position): TPromise; + public openEditor(input: IResourceInputType, sideBySide?: boolean): TPromise; + public openEditor(input: any, arg2?: any, arg3?: any): TPromise { + if (!input) { + return TPromise.as(null); + } + + // Workbench Input Support + if (input instanceof EditorInput) { + return this.doOpenEditor(input, this.toOptions(arg2), arg3); + } + + // Support opening foreign resources (such as a http link that points outside of the workbench) + const resourceInput = input; + if (resourceInput.resource instanceof URI) { + const schema = resourceInput.resource.scheme; + if (schema === network.Schemas.http || schema === network.Schemas.https) { + window.open(resourceInput.resource.toString(true)); + + return TPromise.as(null); + } + } + + // Untyped Text Editor Support (required for code that uses this service below workbench level) + const textInput = input; + const typedInput = this.createInput(textInput); + if (typedInput) { + return this.doOpenEditor(typedInput, TextEditorOptions.from(textInput), arg2); + } + + return TPromise.as(null); + } + + private toOptions(options?: IEditorOptions | EditorOptions): EditorOptions { + if (!options || options instanceof EditorOptions) { + return options as EditorOptions; + } + + const textOptions: ITextEditorOptions = options; + if (!!textOptions.selection) { + return TextEditorOptions.create(options); + } + + return EditorOptions.create(options); + } + + /** + * Allow subclasses to implement their own behavior for opening editor (see below). + */ + protected doOpenEditor(input: IEditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; + protected doOpenEditor(input: IEditorInput, options?: EditorOptions, position?: Position): TPromise; + protected doOpenEditor(input: IEditorInput, options?: EditorOptions, arg3?: any): TPromise { + return this.editorPart.openEditor(input, options, arg3); + } + + public openEditors(editors: { input: IResourceInputType, position: Position }[]): TPromise; + public openEditors(editors: { input: IEditorInput, position: Position, options?: IEditorOptions }[]): TPromise; + public openEditors(editors: any[]): TPromise { + const inputs = editors.map(editor => this.createInput(editor.input)); + const typedInputs: { input: IEditorInput, position: Position, options?: EditorOptions }[] = inputs.map((input, index) => { + const options = editors[index].input instanceof EditorInput ? this.toOptions(editors[index].options) : TextEditorOptions.from(editors[index].input); + + return { + input, + options, + position: editors[index].position + }; + }); + + return this.editorPart.openEditors(typedInputs); + } + + public replaceEditors(editors: { toReplace: IResourceInputType, replaceWith: IResourceInputType }[], position?: Position): TPromise; + public replaceEditors(editors: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: IEditorOptions }[], position?: Position): TPromise; + public replaceEditors(editors: any[], position?: Position): TPromise { + const toReplaceInputs = editors.map(editor => this.createInput(editor.toReplace)); + const replaceWithInputs = editors.map(editor => this.createInput(editor.replaceWith)); + const typedReplacements: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: EditorOptions }[] = editors.map((editor, index) => { + const options = editor.toReplace instanceof EditorInput ? this.toOptions(editor.options) : TextEditorOptions.from(editor.replaceWith); + + return { + toReplace: toReplaceInputs[index], + replaceWith: replaceWithInputs[index], + options + }; + }); + + return this.editorPart.replaceEditors(typedReplacements, position); + } + + public closeEditor(position: Position, input: IEditorInput): TPromise { + return this.doCloseEditor(position, input); + } + + protected doCloseEditor(position: Position, input: IEditorInput): TPromise { + return this.editorPart.closeEditor(position, input); + } + + public closeEditors(position: Position, filter?: { except?: IEditorInput, direction?: Direction, unmodifiedOnly?: boolean }): TPromise { + return this.editorPart.closeEditors(position, filter); + } + + public closeAllEditors(except?: Position): TPromise { + return this.editorPart.closeAllEditors(except); + } + + public createInput(input: IEditorInput): EditorInput; + public createInput(input: IResourceInputType): EditorInput; + public createInput(input: any): IEditorInput { + + // Workbench Input Support + if (input instanceof EditorInput) { + return input; + } + + // Side by Side Support + const resourceSideBySideInput = input; + if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) { + const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource }); + const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource }); + + return new SideBySideEditorInput(resourceSideBySideInput.label || masterInput.getName(), typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(), detailInput, masterInput); + } + + // Diff Editor Support + const resourceDiffInput = input; + if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) { + const leftInput = this.createInput({ resource: resourceDiffInput.leftResource }); + const rightInput = this.createInput({ resource: resourceDiffInput.rightResource }); + const label = resourceDiffInput.label || this.toDiffLabel(resourceDiffInput.leftResource, resourceDiffInput.rightResource, this.workspaceContextService, this.environmentService); + + return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput); + } + + // Untitled file support + const untitledInput = input; + if (!untitledInput.resource || typeof untitledInput.filePath === 'string' || (untitledInput.resource instanceof URI && untitledInput.resource.scheme === UNTITLED_SCHEMA)) { + return this.untitledEditorService.createOrGet(untitledInput.filePath ? URI.file(untitledInput.filePath) : untitledInput.resource, untitledInput.language, untitledInput.contents, untitledInput.encoding); + } + + const resourceInput = input; + + // Files support + if (resourceInput.resource instanceof URI && resourceInput.resource.scheme === network.Schemas.file) { + return this.createOrGet(resourceInput.resource, this.instantiationService, resourceInput.label, resourceInput.description, resourceInput.encoding); + } + + // Any other resource + else if (resourceInput.resource instanceof URI) { + const label = resourceInput.label || basename(resourceInput.resource.fsPath); + let description: string; + if (typeof resourceInput.description === 'string') { + description = resourceInput.description; + } else if (resourceInput.resource.scheme === network.Schemas.file) { + description = dirname(resourceInput.resource.fsPath); + } + + return this.createOrGet(resourceInput.resource, this.instantiationService, label, description); + } + + return null; + } + + private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string, description: string, encoding?: string): ICachedEditorInput { + if (WorkbenchEditorService.CACHE.has(resource)) { + const input = WorkbenchEditorService.CACHE.get(resource); + if (input instanceof ResourceEditorInput) { + input.setName(label); + input.setDescription(description); + } else { + input.setPreferredEncoding(encoding); + } + + return input; + } + + let input: ICachedEditorInput; + if (resource.scheme === network.Schemas.file || this.fileService.canHandleResource && this.fileService.canHandleResource(resource)) { + input = this.fileInputFactory.createFileInput(resource, encoding, instantiationService); + } else { + input = instantiationService.createInstance(ResourceEditorInput, label, description, resource); + } + + WorkbenchEditorService.CACHE.set(resource, input); + once(input.onDispose)(() => { + WorkbenchEditorService.CACHE.delete(resource); + }); + + return input; + } + + private toDiffLabel(res1: URI, res2: URI, context: IWorkspaceContextService, environment: IEnvironmentService): string { + const leftName = getPathLabel(res1.fsPath, context, environment); + const rightName = getPathLabel(res2.fsPath, context, environment); + + return nls.localize('compareLabels', "{0} ↔ {1}", leftName, rightName); + } +} + +export interface IEditorOpenHandler { + (input: IEditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; + (input: IEditorInput, options?: EditorOptions, position?: Position): TPromise; +} + +export interface IEditorCloseHandler { + (position: Position, input: IEditorInput): TPromise; +} + +/** + * Subclass of workbench editor service that delegates all calls to the provided editor service. Subclasses can choose to override the behavior + * of openEditor() and closeEditor() by providing a handler. + * + * This gives clients a chance to override the behavior of openEditor() and closeEditor(). + */ +export class DelegatingWorkbenchEditorService extends WorkbenchEditorService { + private editorOpenHandler: IEditorOpenHandler; + private editorCloseHandler: IEditorCloseHandler; + + constructor( + @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IInstantiationService instantiationService: IInstantiationService, + @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService + ) { + super( + editorService, + untitledEditorService, + workspaceContextService, + instantiationService, + environmentService, + fileService + ); + } + + public setEditorOpenHandler(handler: IEditorOpenHandler): void { + this.editorOpenHandler = handler; + } + + public setEditorCloseHandler(handler: IEditorCloseHandler): void { + this.editorCloseHandler = handler; + } + + protected doOpenEditor(input: IEditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; + protected doOpenEditor(input: IEditorInput, options?: EditorOptions, position?: Position): TPromise; + protected doOpenEditor(input: IEditorInput, options?: EditorOptions, arg3?: any): TPromise { + const handleOpen = this.editorOpenHandler ? this.editorOpenHandler(input, options, arg3) : TPromise.as(void 0); + + return handleOpen.then(editor => { + if (editor) { + return TPromise.as(editor); + } + + return super.doOpenEditor(input, options, arg3); + }); + } + + protected doCloseEditor(position: Position, input: IEditorInput): TPromise { + const handleClose = this.editorCloseHandler ? this.editorCloseHandler(position, input) : TPromise.as(void 0); + + return handleClose.then(() => { + return super.doCloseEditor(position, input); + }); + } +} diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index c6ce1376dc1..dabe18285d6 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -14,7 +14,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorOptions, TextEditorOptions } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { DelegatingWorkbenchEditorService, WorkbenchEditorService, IEditorPart } from 'vs/workbench/services/editor/browser/editorService'; +import { DelegatingWorkbenchEditorService, WorkbenchEditorService, IEditorPart } from 'vs/workbench/services/editor/common/editorService'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index d2c61424a26..270f2367a64 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -119,7 +119,7 @@ export class FileService implements IFileService { this.toUnbind.push(this.raw.onAfterOperation(e => this._onAfterOperation.fire(e))); // Config changes - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration()))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration()))); // Root changes this.toUnbind.push(this.contextService.onDidChangeWorkspaceFolders(() => this.onDidChangeWorkspaceFolders())); diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index 1b4f1bcd61d..6de890c430f 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -283,12 +283,19 @@ export class RemoteFileService extends FileService { stream.write(chunk); offset += chunk.length; } - provider.read(resource, offset, Number.MAX_VALUE, new Progress(chunk => stream.write(chunk))).then(() => { + if (offset < count) { + // we didn't read enough the first time which means + // that we are done stream.end(); - }, err => { - stream.emit('error', err); - stream.end(); - }); + } else { + // there is more to read + provider.read(resource, offset, -1, new Progress(chunk => stream.write(chunk))).then(() => { + stream.end(); + }, err => { + stream.emit('error', err); + stream.end(); + }); + } return { encoding: preferredEncoding, diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index 9e3744b2638..6c54ae8d675 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -83,7 +83,7 @@ export class FileWatcher { // Start watching this.updateFolders(); this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateFolders())); - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(() => this.updateFolders())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(() => this.updateFolders())); return () => this.dispose(); } @@ -95,7 +95,7 @@ export class FileWatcher { this.service.setRoots(this.contextService.getWorkspace().folders.map(folder => { // Fetch the root's watcherExclude setting and return it - const configuration = this.configurationService.getConfiguration(undefined, { + const configuration = this.configurationService.getConfiguration({ resource: folder.uri }); let ignored: string[] = []; diff --git a/src/vs/workbench/services/group/common/groupService.ts b/src/vs/workbench/services/group/common/groupService.ts index 12820ea98ea..1cdeceef1ff 100644 --- a/src/vs/workbench/services/group/common/groupService.ts +++ b/src/vs/workbench/services/group/common/groupService.ts @@ -7,7 +7,7 @@ import { createDecorator, ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Position, IEditorInput } from 'vs/platform/editor/common/editor'; -import { IEditorStacksModel, IEditorGroup } from 'vs/workbench/common/editor'; +import { IEditorStacksModel, IEditorGroup, IEditorOpeningEvent } from 'vs/workbench/common/editor'; import Event from 'vs/base/common/event'; export enum GroupArrangement { @@ -45,6 +45,11 @@ export interface IEditorGroupService { */ onEditorsChanged: Event; + /** + * Emitted when an editor is opening. Allows to prevent/replace the opening via the event method. + */ + onEditorOpening: Event; + /** * Emitted when opening an editor fails. */ diff --git a/src/vs/workbench/services/hash/common/hashService.ts b/src/vs/workbench/services/hash/common/hashService.ts new file mode 100644 index 00000000000..e8e2ee18d9d --- /dev/null +++ b/src/vs/workbench/services/hash/common/hashService.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IHashService = createDecorator('hashService'); + +export interface IHashService { + _serviceBrand: any; + + /** + * Produce a SHA1 hash of the provided content. + */ + createSHA1(content: string): string; +} \ No newline at end of file diff --git a/src/vs/workbench/services/hash/node/hashService.ts b/src/vs/workbench/services/hash/node/hashService.ts new file mode 100644 index 00000000000..fec0cab111f --- /dev/null +++ b/src/vs/workbench/services/hash/node/hashService.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { createHash } from 'crypto'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; + +export class HashService implements IHashService { + + _serviceBrand: any; + + public createSHA1(content: string): string { + return createHash('sha1').update(content).digest('hex'); + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index ff937e3303c..6b3a56407ef 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -224,7 +224,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic private getExcludes(root?: URI): IExpression { const scope = root ? { resource: root } : void 0; - return getExcludes(this.configurationService.getConfiguration(void 0, scope)); + return getExcludes(this.configurationService.getConfiguration(scope)); } private registerListeners(): void { diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 4e262196b68..823f3f5f073 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -5,7 +5,7 @@ 'use strict'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IResourceInput, ITextEditorSelection } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IResourceInput } from 'vs/platform/editor/common/editor'; import URI from 'vs/base/common/uri'; export const IHistoryService = createDecorator('historyService'); @@ -19,11 +19,6 @@ export interface IHistoryService { */ reopenLastClosedEditor(): void; - /** - * Add an entry to the navigation stack of the history. - */ - add(input: IEditorInput, selection?: ITextEditorSelection): void; - /** * Navigate forwards in history. * diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 9634a27da63..562e8972512 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -268,7 +268,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { super(contextKeyService, commandService, telemetryService, messageService, statusBarService); let dispatchConfig = getDispatchConfig(configurationService); - configurationService.onDidUpdateConfiguration((e) => { + configurationService.onDidChangeConfiguration((e) => { let newDispatchConfig = getDispatchConfig(configurationService); if (dispatchConfig === newDispatchConfig) { return; diff --git a/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts index 9ee62bfdc44..a3f9d2f1c0f 100644 --- a/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts @@ -16,7 +16,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { KeyCode, SimpleKeybinding, ChordKeybinding } from 'vs/base/common/keyCodes'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import extfs = require('vs/base/node/extfs'); -import { TestTextFileService, TestEditorGroupService, TestLifecycleService, TestBackupFileService, TestContextService, TestTextResourceConfigurationService } from 'vs/workbench/test/workbenchTestServices'; +import { TestTextFileService, TestEditorGroupService, TestLifecycleService, TestBackupFileService, TestContextService, TestTextResourceConfigurationService, TestHashService } from 'vs/workbench/test/workbenchTestServices'; import { IWorkspaceContextService, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import uuid = require('vs/base/common/uuid'); import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; @@ -42,6 +42,7 @@ import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/comm import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; interface Modifiers { metaKey?: boolean; @@ -67,8 +68,10 @@ suite('Keybindings Editing', () => { instantiationService.stub(IConfigurationService, ConfigurationService); instantiationService.stub(IConfigurationService, 'getConfiguration', { 'eol': '\n' }); instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', () => { }); + instantiationService.stub(IConfigurationService, 'onDidChangeConfiguration', () => { }); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IHashService, new TestHashService()); instantiationService.stub(IEditorGroupService, new TestEditorGroupService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModeService, ModeServiceImpl); diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index 7b59087af60..abd67ba5a18 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -123,7 +123,7 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl { }); - this._configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(this._configurationService.getConfiguration())); + this._configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(this._configurationService.getConfiguration())); this.onDidCreateMode((mode) => { this._extensionService.activateByEvent(`onLanguage:${mode.getId()}`).done(null, onUnexpectedError); diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index a9337cf69b8..b659bfeedc4 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -92,4 +92,4 @@ export interface ISCMService { readonly repositories: ISCMRepository[]; registerSCMProvider(provider: ISCMProvider): ISCMRepository; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index ff457516718..02e7c5e570c 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -53,6 +53,7 @@ export class FileWalker { private normalizedFilePatternLowercase: string; private includePattern: glob.ParsedExpression; private maxResults: number; + private exists: boolean; private maxFilesize: number; private isLimitHit: boolean; private resultCount: number; @@ -77,6 +78,7 @@ export class FileWalker { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || null; + this.exists = config.exists; this.maxFilesize = config.maxFilesize || null; this.walkedPaths = Object.create(null); this.resultCount = 0; @@ -234,6 +236,7 @@ export class FileWalker { return; } if (this.isLimitHit) { + done(); return; } @@ -392,9 +395,12 @@ export class FileWalker { cmd.on('close', (code: number) => { // ripgrep returns code=1 when no results are found - if (code !== 0 && ((isRipgrep && stderr.length) || !isRipgrep)) { + if (code !== 0 && (!isRipgrep || code !== 1)) { done(new Error(`command failed with error code ${code}: ${this.decodeData(stderr, encoding)}`)); } else { + if (isRipgrep && this.exists && code === 0) { + this.isLimitHit = true; + } done(null, '', true); } }); @@ -657,7 +663,7 @@ export class FileWalker { if (this.isFilePatternMatch(candidate.relativePath) && (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename))) { this.resultCount++; - if (this.maxResults && this.resultCount > this.maxResults) { + if (this.exists || (this.maxResults && this.resultCount > this.maxResults)) { this.isLimitHit = true; } diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 12c6c6e0381..55fb059c48f 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -23,7 +23,7 @@ import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/text import { IRawSearchService, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchProgressItem, ISerializedSearchComplete, ISearchEngine, IFileSearchProgressItem, ITelemetryEvent } from './search'; import { ICachedSearchStats, IProgress } from 'vs/platform/search/common/search'; import { fuzzyContains } from 'vs/base/common/strings'; -import { compareItemsByScore, IItemAccessor, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { compareItemsByScore, IItemAccessor, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; export class SearchService implements IRawSearchService { @@ -254,7 +254,8 @@ export class SearchService implements IRawSearchService { // this is very important because we are also limiting the number of results by config.maxResults // and as such we want the top items to be included in this result set if the number of items // exceeds config.maxResults. - const compare = (matchA: IRawFileMatch, matchB: IRawFileMatch) => compareItemsByScore(matchA, matchB, strings.stripWildcards(config.filePattern), true, FileMatchItemAccessor, scorerCache); + const query = prepareQuery(config.filePattern); + const compare = (matchA: IRawFileMatch, matchB: IRawFileMatch) => compareItemsByScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache); return arrays.topAsync(results, compare, config.maxResults, 10000); } diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index 7b4b508b4e4..caba0ebd233 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -44,6 +44,10 @@ function getRgArgs(config: IRawSearch, folderQuery: IFolderSearch, includePatter // Follow symlinks args.push('--follow'); + if (config.exists) { + args.push('--quiet'); + } + // Folder to search args.push('--'); diff --git a/src/vs/workbench/services/search/node/search.ts b/src/vs/workbench/services/search/node/search.ts index 424b3b4f0f7..38c3be3415c 100644 --- a/src/vs/workbench/services/search/node/search.ts +++ b/src/vs/workbench/services/search/node/search.ts @@ -25,6 +25,7 @@ export interface IRawSearch { includePattern?: IExpression; contentPattern?: IPatternInfo; maxResults?: number; + exists?: boolean; sortByScore?: boolean; cacheKey?: string; maxFilesize?: number; diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 8fdb18b45af..85c20bd1f10 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -15,13 +15,14 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IRawSearch, IFolderSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService, ITelemetryEvent } from './search'; +import { IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService, ITelemetryEvent } from './search'; import { ISearchChannel, SearchChannelClient } from './searchIpc'; import { IEnvironmentService, IDebugParams } from 'vs/platform/environment/common/environment'; import { ResourceMap } from 'vs/base/common/map'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Schemas } from 'vs/base/common/network'; export class SearchService implements ISearchService { public _serviceBrand: any; @@ -259,25 +260,40 @@ export class DiskSearch implements ISearchResultProvider { let request: PPromise; let rawSearch: IRawSearch = { - folderQueries: query.folderQueries ? query.folderQueries.map(q => { - return { - excludePattern: q.excludePattern, - includePattern: q.includePattern, - fileEncoding: q.fileEncoding, - folder: q.folder.fsPath - }; - }) : [], - extraFiles: query.extraFileResources ? query.extraFileResources.map(r => r.fsPath) : [], + folderQueries: [], + extraFiles: [], filePattern: query.filePattern, excludePattern: query.excludePattern, includePattern: query.includePattern, maxResults: query.maxResults, + exists: query.exists, sortByScore: query.sortByScore, cacheKey: query.cacheKey, useRipgrep: query.useRipgrep, disregardIgnoreFiles: query.disregardIgnoreFiles }; + if (query.folderQueries) { + for (const q of query.folderQueries) { + if (q.folder.scheme === Schemas.file) { + rawSearch.folderQueries.push({ + excludePattern: q.excludePattern, + includePattern: q.includePattern, + fileEncoding: q.fileEncoding, + folder: q.folder.fsPath + }); + } + } + } + + if (query.extraFileResources) { + for (const r of query.extraFileResources) { + if (r.scheme === Schemas.file) { + rawSearch.extraFiles.push(r.fsPath); + } + } + } + if (query.type === QueryType.Text) { rawSearch.contentPattern = query.contentPattern; } diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index abecd589774..c2ce634c8d1 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -84,6 +84,88 @@ suite('FileSearchEngine', () => { }); }); + test('Files: exists', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/file.txt': true }, + exists: true + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(complete.limitHit); + done(); + }); + }); + + test('Files: not exists', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/nofile.txt': true }, + exists: true + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(!complete.limitHit); + done(); + }); + }); + + test('Files: exists without Ripgrep', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/file.txt': true }, + exists: true, + useRipgrep: false + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(complete.limitHit); + done(); + }); + }); + + test('Files: not exists without Ripgrep', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/nofile.txt': true }, + exists: true, + useRipgrep: false + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(!complete.limitHit); + done(); + }); + }); + test('Files: examples/com*', function (done: () => void) { let engine = new FileSearchEngine({ folderQueries: ROOT_FOLDER_QUERY, @@ -138,6 +220,53 @@ suite('FileSearchEngine', () => { }); }); + test('Files: multiroot with includePattern and maxResults', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: MULTIROOT_QUERIES, + maxResults: 1, + includePattern: { + '*.txt': true, + '*.js': true + }, + useRipgrep: true + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 1); + done(); + }); + }); + + test('Files: multiroot with includePattern and exists', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: MULTIROOT_QUERIES, + exists: true, + includePattern: { + '*.txt': true, + '*.js': true + }, + useRipgrep: true + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(complete.limitHit); + done(); + }); + }); + test('Files: NPE (CamelCase)', function (done: () => void) { let engine = new FileSearchEngine({ folderQueries: ROOT_FOLDER_QUERY, diff --git a/src/vs/workbench/services/search/test/node/searchService.test.ts b/src/vs/workbench/services/search/test/node/searchService.test.ts index 36935227c65..7fd40591eef 100644 --- a/src/vs/workbench/services/search/test/node/searchService.test.ts +++ b/src/vs/workbench/services/search/test/node/searchService.test.ts @@ -7,9 +7,10 @@ import * as assert from 'assert'; import { normalize } from 'path'; +import path = require('path'); import { IProgress, IUncachedSearchStats } from 'vs/platform/search/common/search'; -import { ISearchEngine, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchComplete } from 'vs/workbench/services/search/node/search'; +import { ISearchEngine, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchComplete, IFolderSearch } from 'vs/workbench/services/search/node/search'; import { SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; @@ -17,6 +18,12 @@ const TEST_FOLDER_QUERIES = [ { folder: normalize('/some/where') } ]; +const TEST_FIXTURES = path.normalize(require.toUrl('./fixtures')); +const MULTIROOT_QUERIES: IFolderSearch[] = [ + { folder: path.join(TEST_FIXTURES, 'examples') }, + { folder: path.join(TEST_FIXTURES, 'more') } +]; + const stats: IUncachedSearchStats = { fromCache: false, resultCount: 4, @@ -143,6 +150,43 @@ suite('SearchService', () => { }); }); + test('Multi-root with include pattern and maxResults', function () { + const service = new RawSearchService(); + + const query: IRawSearch = { + folderQueries: MULTIROOT_QUERIES, + maxResults: 1, + includePattern: { + '*.txt': true, + '*.js': true + }, + }; + + return DiskSearch.collectResults(service.fileSearch(query)) + .then(result => { + assert.strictEqual(result.results.length, 1, 'Result'); + }); + }); + + test('Multi-root with include pattern and exists', function () { + const service = new RawSearchService(); + + const query: IRawSearch = { + folderQueries: MULTIROOT_QUERIES, + exists: true, + includePattern: { + '*.txt': true, + '*.js': true + }, + }; + + return DiskSearch.collectResults(service.fileSearch(query)) + .then(result => { + assert.strictEqual(result.results.length, 0, 'Result'); + assert.ok(result.limitHit); + }); + }); + test('Sorted results', function () { const paths = ['bab', 'bbc', 'abb']; const matches: IRawFileMatch[] = paths.map(relativePath => ({ diff --git a/src/vs/workbench/services/telemetry/common/workspaceStats.ts b/src/vs/workbench/services/telemetry/node/workspaceStats.ts similarity index 95% rename from src/vs/workbench/services/telemetry/common/workspaceStats.ts rename to src/vs/workbench/services/telemetry/node/workspaceStats.ts index 5bdecfc2d37..e81862ca195 100644 --- a/src/vs/workbench/services/telemetry/common/workspaceStats.ts +++ b/src/vs/workbench/services/telemetry/node/workspaceStats.ts @@ -36,7 +36,7 @@ const SecondLevelDomainWhitelist = [ 'google.com' ]; -type Tags = { [index: string]: boolean | number }; +type Tags = { [index: string]: boolean | number | string }; function stripLowLevelDomains(domain: string): string { let match = domain.match(SecondLevelDomainMatcher); @@ -149,6 +149,7 @@ export class WorkspaceStats { "workbench.filesToOpen" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "workbench.filesToCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "workspace.id" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, "workspace.roots" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, "workspace.empty" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, "workspace.grunt" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, @@ -175,13 +176,29 @@ export class WorkspaceStats { private getWorkspaceTags(configuration: IWindowConfiguration): TPromise { const tags: Tags = Object.create(null); - const { filesToOpen, filesToCreate, filesToDiff } = configuration; - tags['workbench.filesToOpen'] = filesToOpen && filesToOpen.length || undefined; - tags['workbench.filesToCreate'] = filesToCreate && filesToCreate.length || undefined; - tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || undefined; - - const isEmpty = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; + const state = this.contextService.getWorkbenchState(); const workspace = this.contextService.getWorkspace(); + + let workspaceId: string; + switch (state) { + case WorkbenchState.EMPTY: + workspaceId = void 0; + break; + case WorkbenchState.FOLDER: + workspaceId = crypto.createHash('sha1').update(workspace.folders[0].uri.fsPath).digest('hex'); + break; + case WorkbenchState.WORKSPACE: + workspaceId = crypto.createHash('sha1').update(workspace.configuration.fsPath).digest('hex'); + } + + tags['workspace.id'] = workspaceId; + + const { filesToOpen, filesToCreate, filesToDiff } = configuration; + tags['workbench.filesToOpen'] = filesToOpen && filesToOpen.length || 0; + tags['workbench.filesToCreate'] = filesToCreate && filesToCreate.length || 0; + tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0; + + const isEmpty = state === WorkbenchState.EMPTY; tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length; tags['workspace.empty'] = isEmpty; diff --git a/src/vs/workbench/services/telemetry/test/workspaceStats.test.ts b/src/vs/workbench/services/telemetry/test/workspaceStats.test.ts index 5647af87623..7bb6e7d3d8f 100644 --- a/src/vs/workbench/services/telemetry/test/workspaceStats.test.ts +++ b/src/vs/workbench/services/telemetry/test/workspaceStats.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as crypto from 'crypto'; -import { getDomainsOfRemotes, getRemotes, getHashedRemotes } from 'vs/workbench/services/telemetry/common/workspaceStats'; +import { getDomainsOfRemotes, getRemotes, getHashedRemotes } from 'vs/workbench/services/telemetry/node/workspaceStats'; function hash(value: string): string { return crypto.createHash('sha1').update(value.toString()).digest('hex'); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 3267637c435..6c3cd249763 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -31,9 +31,9 @@ import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { anonymize } from 'vs/platform/telemetry/common/telemetryUtils'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IRawTextSource } from 'vs/editor/common/model/textSource'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; /** * The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk. @@ -87,6 +87,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil @IBackupFileService private backupFileService: IBackupFileService, @IEnvironmentService private environmentService: IEnvironmentService, @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IHashService private hashService: IHashService ) { super(modelService, modeService); @@ -364,16 +365,31 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private loadWithContent(content: IRawTextContent | IContent, backup?: URI): TPromise { - diag('load() - resolved content', this.resource, new Date()); + return this.doLoadWithContent(content, backup).then(model => { - /* __GDPR__ - "fileGet" : { - "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "path": { "classification": "CustomerContent", "purpose": "FeatureInsight" } + // Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype + if (this.isSettingsFile()) { + /* __GDPR__ + "settingsRead" : {} + */ + this.telemetryService.publicLog('settingsRead'); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data + } else { + /* __GDPR__ + "fileGet" : { + "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "path": { "classification": "CustomerContent", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.resource.fsPath), path: this.hashService.createSHA1(this.resource.fsPath) }); } - */ - this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.resource.fsPath), path: anonymize(this.resource.fsPath) }); + + return model; + }); + } + + private doLoadWithContent(content: IRawTextContent | IContent, backup?: URI): TPromise { + diag('load() - resolved content', this.resource, new Date()); // Update our resolved disk stat model const resolvedStat: IFileStat = { @@ -765,7 +781,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.contextService.getWorkspace().folders.some(folder => { return paths.isEqualOrParent(this.resource.fsPath, path.join(folder.uri.fsPath, '.vscode')); }); - } private doTouch(): TPromise { diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 5dfbcde38a9..27a19245296 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -124,7 +124,7 @@ export abstract class TextFileService implements ITextFileService { this.lifecycleService.onShutdown(this.dispose, this); // Configuration changes - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration()))); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration()))); } private beforeShutdown(reason: ShutdownReason): boolean | TPromise { diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 777b42a5b71..38b5749c179 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -19,7 +19,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; class ServiceAccessor { constructor( @@ -380,7 +380,7 @@ suite('Files - TextFileService', () => { service.onConfigurationChange({ files: { hotExit: setting } }); // Set empty workspace if required if (!workspace) { - accessor.contextService.setWorkspace(null); + accessor.contextService.setWorkspace(new Workspace('empty:1508317022751')); } // Set multiple windows if required if (multipleWindows) { diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index 5c6b7609512..4e7c779283c 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -121,8 +121,12 @@ export const tokenColorizationSettingSchema: IJSONSchema = { foreground: { type: 'string', description: nls.localize('schema.token.foreground', 'Foreground color for the token.'), - format: 'color', - defaultSnippets: [{ body: '${1:#FF0000}' }] + format: 'color-hex', + default: '#ff0000' + }, + background: { + type: 'string', + deprecationMessage: nls.localize('schema.token.background.warning', 'Token background colors are currently not supported.') }, fontStyle: { type: 'string', diff --git a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts index 263f48a9b35..b26a44c5ab7 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts @@ -167,7 +167,7 @@ const schema: IJSONSchema = { }, fontColor: { type: 'string', - format: 'color', + format: 'color-hex', description: nls.localize('schema.fontColor', 'When using a glyph font: The color to use.') }, fontSize: { @@ -209,6 +209,10 @@ const schema: IJSONSchema = { highContrast: { $ref: '#/definitions/associations', description: nls.localize('schema.highContrast', 'Optional associations for file icons in high contrast color themes.') + }, + hidesExplorerArrows: { + type: 'boolean', + description: nls.localize('schema.hidesExplorerArrows', 'Configures whether the file explorer\'s arrows should be hidden when this theme is active.') } } }; diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index dfacea5a13c..f8f97e35a74 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -7,9 +7,9 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; import Event from 'vs/base/common/event'; -import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { Color } from 'vs/base/common/color'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export const IWorkbenchThemeService = createDecorator('themeService'); @@ -47,6 +47,7 @@ export interface IFileIconTheme { readonly isLoaded: boolean; readonly hasFileIcons?: boolean; readonly hasFolderIcons?: boolean; + readonly hidesExplorerArrows?: boolean; } export interface IWorkbenchThemeService extends IThemeService { diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts index fcdd7f82d21..286627526d4 100644 --- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts @@ -21,6 +21,7 @@ export class FileIconThemeData implements IFileIconTheme { description?: string; hasFileIcons?: boolean; hasFolderIcons?: boolean; + hidesExplorerArrows?: boolean; isLoaded: boolean; path?: string; extensionData: ExtensionData; @@ -38,6 +39,7 @@ export class FileIconThemeData implements IFileIconTheme { this.styleSheetContent = result.content; this.hasFileIcons = result.hasFileIcons; this.hasFolderIcons = result.hasFolderIcons; + this.hidesExplorerArrows = result.hidesExplorerArrows; this.isLoaded = true; return this.styleSheetContent; }); @@ -69,6 +71,7 @@ export class FileIconThemeData implements IFileIconTheme { themeData.settingsId = null; themeData.hasFileIcons = false; themeData.hasFolderIcons = false; + themeData.hidesExplorerArrows = false; themeData.isLoaded = true; themeData.extensionData = null; } @@ -110,6 +113,7 @@ interface IconThemeDocument extends IconsAssociation { fonts: FontDefinition[]; light?: IconsAssociation; highContrast?: IconsAssociation; + hidesExplorerArrows?: boolean; } function _loadIconThemeDocument(fileSetPath: string): TPromise { @@ -123,9 +127,9 @@ function _loadIconThemeDocument(fileSetPath: string): TPromise { colorThemeSettingSchema.enum = themes.map(t => t.settingsId); colorThemeSettingSchema.enumDescriptions = themes.map(t => themeData.description || ''); - configurationRegistry.notifyConfigurationSchemaUpdated(colorThemeSettingSchema); + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); }); this.iconThemeStore.onDidChange(themes => { - iconThemeSettingSchema.enum = themes.map(t => t.settingsId); - iconThemeSettingSchema.enumDescriptions = themes.map(t => themeData.description || ''); - configurationRegistry.notifyConfigurationSchemaUpdated(iconThemeSettingSchema); + iconThemeSettingSchema.enum = [null, ...themes.map(t => t.settingsId)]; + iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions[0], ...themes.map(t => themeData.description || '')]; + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); }); } @@ -222,8 +221,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.updateColorCustomizations(false); - let colorThemeSetting = this.configurationService.lookup(COLOR_THEME_SETTING).value; - let iconThemeSetting = this.configurationService.lookup(ICON_THEME_SETTING).value || ''; + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING) || ''; return Promise.join([ this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { @@ -236,8 +235,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private installConfigurationListener() { - this.configurationService.onDidUpdateConfiguration(e => { - let colorThemeSetting = this.configurationService.lookup(COLOR_THEME_SETTING).value; + this.configurationService.onDidChangeConfiguration(e => { + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, null).then(theme => { if (theme) { @@ -246,7 +245,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } - let iconThemeSetting = this.configurationService.lookup(ICON_THEME_SETTING).value || ''; + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING) || ''; if (iconThemeSetting !== this.currentIconTheme.settingsId) { this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { this.setFileIconTheme(theme && theme.id, null); @@ -375,10 +374,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private updateColorCustomizations(notify = true): void { - let newColorCustomizations = this.configurationService.lookup(CUSTOM_WORKBENCH_COLORS_SETTING).value || {}; + let newColorCustomizations = this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING) || {}; let newColorIds = Object.keys(newColorCustomizations); - let newTokenColorCustomizations = this.configurationService.lookup(CUSTOM_EDITOR_COLORS_SETTING).value || {}; + let newTokenColorCustomizations = this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; if (this.hasCustomizationChanged(newColorCustomizations, newColorIds, newTokenColorCustomizations)) { this.colorCustomizations = newColorCustomizations; @@ -507,11 +506,11 @@ colorThemeSchema.register(); fileIconThemeSchema.register(); class ConfigurationWriter { - constructor( @IConfigurationService private configurationService: IConfigurationService, @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService) { + constructor( @IConfigurationService private configurationService: IConfigurationService) { } public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget): TPromise { - let settings = this.configurationService.lookup(key); + let settings = this.configurationService.inspect(key); if (settingsTarget === ConfigurationTarget.USER) { if (value === settings.user) { return TPromise.as(null); // nothing to do @@ -526,14 +525,14 @@ class ConfigurationWriter { return TPromise.as(null); // nothing to do } } - return this.configurationEditingService.writeConfiguration(settingsTarget, { key, value }); + return this.configurationService.updateValue(key, value, settingsTarget); } } // Configuration: Themes const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); -const colorThemeSettingSchema: IJSONSchema = { +const colorThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), default: DEFAULT_THEME_SETTING_VALUE, @@ -542,7 +541,7 @@ const colorThemeSettingSchema: IJSONSchema = { errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; -const iconThemeSettingSchema: IJSONSchema = { +const iconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], default: DEFAULT_ICON_THEME_SETTING_VALUE, description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."), @@ -550,7 +549,7 @@ const iconThemeSettingSchema: IJSONSchema = { enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')], errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") }; -const colorCustomizationsSchema: IJSONSchema = { +const colorCustomizationsSchema: IConfigurationPropertySchema = { type: ['object'], description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), properties: colorThemeSchema.colorsSchema.properties, @@ -565,7 +564,7 @@ const colorCustomizationsSchema: IJSONSchema = { }] }; -configurationRegistry.registerConfiguration({ +const themeSettingsConfiguration: IConfigurationNode = { id: 'workbench', order: 7.1, type: 'object', @@ -574,7 +573,8 @@ configurationRegistry.registerConfiguration({ [ICON_THEME_SETTING]: iconThemeSettingSchema, [CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema } -}); +}; +configurationRegistry.registerConfiguration(themeSettingsConfiguration); function tokenGroupSettings(description: string) { return { @@ -583,8 +583,7 @@ function tokenGroupSettings(description: string) { anyOf: [ { type: 'string', - format: 'color', - defaultSnippets: [{ body: '#FF0000' }] + format: 'color-hex' }, colorThemeSchema.tokenColorizationSettingSchema ] diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 934b4c70a19..dfe723df71c 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -17,7 +17,7 @@ import { dirname } from 'path'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces'; import { isLinux } from 'vs/base/common/platform'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { StorageService } from 'vs/platform/storage/common/storageService'; @@ -116,11 +116,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } private isSupported(): boolean { - // TODO@Ben multi root - return ( - this.environmentService.appQuality !== 'stable' // not yet enabled in stable - && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE // we need a multi folder workspace to begin with - ); + return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; // we need a multi folder workspace to begin with; } private contains(resources: URI[], toCheck: URI): boolean { @@ -194,7 +190,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { const targetWorkspaceConfiguration = {}; for (const key of this.workspaceConfigurationService.keys().workspace) { if (configurationProperties[key] && !configurationProperties[key].isFromExtensions && configurationProperties[key].scope === ConfigurationScope.WINDOW) { - targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.lookup(key).workspace; + targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace; } } diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index a935f3c9eed..098ad1ccde8 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -11,11 +11,11 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { MainThreadConfigurationShape } from 'vs/workbench/api/node/extHost.protocol'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ConfigurationTarget, ConfigurationEditingErrorCode, ConfigurationEditingError } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { ConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { TestThreadService } from './testThreadService'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; suite('ExtHostConfiguration', function () { @@ -394,7 +394,7 @@ suite('ExtHostConfiguration', function () { const shape = new class extends mock() { $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): TPromise { - return TPromise.wrapError(new ConfigurationEditingError('Unknown Key', ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY)); // something !== OK + return TPromise.wrapError(new Error('Unknown Key')); // something !== OK } }; diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts index 425dd180872..b91637eeeae 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts @@ -13,13 +13,11 @@ import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platf import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { MainThreadConfiguration } from 'vs/workbench/api/electron-browser/mainThreadConfiguration'; -import { ConfigurationTarget, IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; import { OneGetThreadService } from './testThreadService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; -suite('ExtHostConfiguration', function () { +suite('MainThreadConfiguration', function () { let instantiationService: TestInstantiationService; let target: sinon.SinonSpy; @@ -47,12 +45,13 @@ suite('ExtHostConfiguration', function () { }); setup(() => { - instantiationService = new TestInstantiationService(); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - target = sinon.spy(); - instantiationService.stub(IConfigurationEditingService, ConfigurationEditingService); - instantiationService.stub(IConfigurationEditingService, 'writeConfiguration', target); + + instantiationService = new TestInstantiationService(); + instantiationService.stub(IConfigurationService, WorkspaceService); + instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', sinon.mock()); + instantiationService.stub(IConfigurationService, 'onDidChangeConfiguration', sinon.mock()); + instantiationService.stub(IConfigurationService, 'updateValue', target); }); test('update resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { @@ -61,7 +60,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { @@ -70,7 +69,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { @@ -79,7 +78,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { @@ -88,7 +87,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () { @@ -97,7 +96,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { @@ -106,7 +105,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { @@ -115,7 +114,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update resource configuration without configuration target defaults to folder', function () { @@ -124,7 +123,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); - assert.equal(ConfigurationTarget.FOLDER, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); test('update configuration with user configuration target', function () { @@ -133,7 +132,7 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', URI.file('abc')); - assert.equal(ConfigurationTarget.USER, target.args[0][0]); + assert.equal(ConfigurationTarget.USER, target.args[0][3]); }); test('update configuration with workspace configuration target', function () { @@ -142,16 +141,16 @@ suite('ExtHostConfiguration', function () { testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', URI.file('abc')); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('update configuration with folder configuration target', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); - testObject.$updateConfigurationOption(ConfigurationTarget.FOLDER, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', URI.file('abc')); - assert.equal(ConfigurationTarget.FOLDER, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); test('remove resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { @@ -160,7 +159,7 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('remove resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { @@ -169,7 +168,7 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('remove resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { @@ -178,7 +177,7 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('remove window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { @@ -187,7 +186,7 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('remove window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () { @@ -196,7 +195,7 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('remove window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { @@ -205,7 +204,7 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('remove window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { @@ -214,7 +213,7 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); - assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); test('remove configuration without configuration target defaults to folder', function () { @@ -223,6 +222,6 @@ suite('ExtHostConfiguration', function () { testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); - assert.equal(ConfigurationTarget.FOLDER, target.args[0][0]); + assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); }); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index e09df0d1813..5a105e010f7 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -15,7 +15,7 @@ import URI from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService'; -import { IEditorGroup, ConfirmResult } from 'vs/workbench/common/editor'; +import { IEditorGroup, ConfirmResult, IEditorOpeningEvent } from 'vs/workbench/common/editor'; import Event, { Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -24,7 +24,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IEditorInput, IEditorOptions, Position, Direction, IEditor, IResourceInput, ITextEditorSelection } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditorOptions, Position, Direction, IEditor, IResourceInput } from 'vs/platform/editor/common/editor'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IMessageService, IConfirmation, IConfirmationResult } from 'vs/platform/message/common/message'; import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; @@ -59,6 +59,7 @@ import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IPosition } from 'vs/editor/common/core/position'; import { ICommandAction } from 'vs/platform/actions/common/actions'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, void 0); @@ -102,12 +103,14 @@ export class TestContextService implements IWorkspaceContextService { } public getWorkbenchState(): WorkbenchState { - if (this.workspace) { - if (this.workspace.configuration) { - return WorkbenchState.WORKSPACE; - } + if (this.workspace.configuration) { + return WorkbenchState.WORKSPACE; + } + + if (this.workspace.folders.length) { return WorkbenchState.FOLDER; } + return WorkbenchState.EMPTY; } @@ -257,6 +260,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IEnvironmentService, TestEnvironmentService); instantiationService.stub(IThemeService, new TestThemeService()); + instantiationService.stub(IHashService, new TestHashService()); return instantiationService; } @@ -271,9 +275,6 @@ export class TestHistoryService implements IHistoryService { public reopenLastClosedEditor(): void { } - public add(input: IEditorInput, selection?: ITextEditorSelection): void { - } - public forward(acrossEditors?: boolean): void { } @@ -456,6 +457,7 @@ export class TestEditorGroupService implements IEditorGroupService { private stacksModel: EditorStacksModel; private _onEditorsChanged: Emitter; + private _onEditorOpening: Emitter; private _onEditorOpenFail: Emitter; private _onEditorsMoved: Emitter; private _onGroupOrientationChanged: Emitter; @@ -464,6 +466,7 @@ export class TestEditorGroupService implements IEditorGroupService { constructor(callback?: (method: string) => void) { this._onEditorsMoved = new Emitter(); this._onEditorsChanged = new Emitter(); + this._onEditorOpening = new Emitter(); this._onGroupOrientationChanged = new Emitter(); this._onEditorOpenFail = new Emitter(); this._onTabOptionsChanged = new Emitter(); @@ -490,6 +493,10 @@ export class TestEditorGroupService implements IEditorGroupService { return this._onEditorsChanged.event; } + public get onEditorOpening(): Event { + return this._onEditorOpening.event; + } + public get onEditorOpenFail(): Event { return this._onEditorOpenFail.event; } @@ -1208,7 +1215,7 @@ export class TestTextResourceConfigurationService implements ITextResourceConfig constructor(private configurationService = new TestConfigurationService()) { } - public onDidUpdateConfiguration() { + public onDidChangeConfiguration() { return { dispose() { } }; } @@ -1218,4 +1225,12 @@ export class TestTextResourceConfigurationService implements ITextResourceConfig public getConfiguration(resource: any, position?: any, section?: any): any { return this.configurationService.getConfiguration(section, { resource }); } +} + +export class TestHashService implements IHashService { + _serviceBrand: any; + + createSHA1(content: string): string { + return content; + } } \ No newline at end of file diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index b162fb6a023..4844c81d0aa 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -9,6 +9,9 @@ import 'vs/base/common/strings'; import 'vs/base/common/errors'; +// Configuration +import 'vs/workbench/services/configuration/common/configurationExtensionPoint'; + // Editor import 'vs/editor/editor.all'; diff --git a/tslint.json b/tslint.json index d4df6618424..1264f2b5cf0 100644 --- a/tslint.json +++ b/tslint.json @@ -348,15 +348,52 @@ ] }, { - "target": "**/vs/workbench/services/**", + "target": "**/vs/workbench/services/**/common/**", "restrictions": [ "vs/nls", "vs/css!./**/*", - "**/vs/base/**", - "**/vs/platform/**", - "**/vs/editor/**", + "**/vs/base/**/common/**", + "**/vs/platform/**/common/**", + "**/vs/editor/common/**", + "**/vs/workbench/common/**", + "**/vs/workbench/services/**/common/**" + ] + }, + { + "target": "**/vs/workbench/services/**/browser/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser}/**", + "**/vs/platform/**/{common,browser}/**", + "**/vs/editor/{common,browser}/**", + "**/vs/workbench/{common,browser}/**", + "**/vs/workbench/services/**/{common,browser}/**" + ] + }, + { + "target": "**/vs/workbench/services/**/node/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,node}/**", + "**/vs/platform/**/{common,node}/**", + "**/vs/editor/{common,node}/**", + "**/vs/workbench/{common,node}/**", + "**/vs/workbench/services/**/{common,node}/**", + "*" // node modules + ] + }, + { + "target": "**/vs/workbench/services/**/electron-browser/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser,node,electron-browser}/**", + "**/vs/platform/**/{common,browser,node,electron-browser}/**", + "**/vs/editor/**/{common,browser,node,electron-browser}/**", "**/vs/workbench/{common,browser,node,electron-browser,api}/**", - "**/vs/workbench/services/**", + "**/vs/workbench/services/**/{common,browser,node,electron-browser}/**", "*" // node modules ] },