diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index b7d31b812d8d9..301b01e45c73a 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -734,7 +734,7 @@ "--tab-sizing-current-width", "--tab-sizing-fixed-min-width", "--tab-sizing-fixed-max-width", - "--editor-group-title-height", + "--editor-group-tab-height", "--testMessageDecorationFontFamily", "--testMessageDecorationFontSize", "--title-border-bottom-color", diff --git a/extensions/csharp/cgmanifest.json b/extensions/csharp/cgmanifest.json index 962188ea97198..dd44d10fb75f9 100644 --- a/extensions/csharp/cgmanifest.json +++ b/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "772323937fedd65c6dc1c8ce6ea41d97415ed7d1" + "commitHash": "525e628edad54c0f7aa15b015310df240803ea66" } }, "license": "MIT", diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index c08bea9ce0182..908da2f69cce6 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.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/dotnet/csharp-tmLanguage/commit/772323937fedd65c6dc1c8ce6ea41d97415ed7d1", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/525e628edad54c0f7aa15b015310df240803ea66", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -210,9 +210,6 @@ { "include": "#else-part" }, - { - "include": "#switch-statement" - }, { "include": "#goto-statement" }, @@ -238,10 +235,10 @@ "include": "#checked-unchecked-statement" }, { - "include": "#lock-statement" + "include": "#context-control-statement" }, { - "include": "#using-statement" + "include": "#context-control-paren-statement" }, { "include": "#labeled-statement" @@ -278,13 +275,10 @@ "include": "#comment" }, { - "include": "#checked-unchecked-expression" + "include": "#expression-operator-expression" }, { - "include": "#typeof-or-default-expression" - }, - { - "include": "#nameof-expression" + "include": "#type-operator-expression" }, { "include": "#default-literal-expression" @@ -305,14 +299,20 @@ "include": "#type-builtin" }, { - "include": "#this-or-base-expression" + "include": "#language-variable" + }, + { + "include": "#switch-statement-or-expression" }, { - "include": "#switch-expression" + "include": "#with-expression" }, { "include": "#conditional-operator" }, + { + "include": "#assignment-expression" + }, { "include": "#expression-operators" }, @@ -370,36 +370,39 @@ ] }, "extern-alias-directive": { - "begin": "\\s*(extern)\\b\\s*(alias)\\b\\s*(@?[_[:alpha:]][_[:alnum:]]*)", + "begin": "\\b(extern)\\s+(alias)\\b", "beginCaptures": { "1": { - "name": "keyword.other.extern.cs" + "name": "keyword.other.directive.extern.cs" }, "2": { - "name": "keyword.other.alias.cs" - }, - "3": { - "name": "variable.other.alias.cs" + "name": "keyword.other.directive.alias.cs" } }, - "end": "(?=;)" + "end": "(?=;)", + "patterns": [ + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "variable.other.alias.cs" + } + ] }, "using-directive": { "patterns": [ { - "begin": "(\\b(global)\\b\\s+)?\\b(using)\\b\\s+(static)\\b\\s+(\\b(unsafe)\\b\\s+)?", + "begin": "\\b(?:(global)\\s+)?(using)\\s+(static)\\b\\s*(?:(unsafe)\\b\\s*)?", "beginCaptures": { + "1": { + "name": "keyword.other.directive.global.cs" + }, "2": { - "name": "keyword.other.global.cs" + "name": "keyword.other.directive.using.cs" }, "3": { - "name": "keyword.other.using.cs" + "name": "keyword.other.directive.static.cs" }, "4": { - "name": "keyword.other.static.cs" - }, - "6": { - "name": "storage.modifier.cs" + "name": "storage.modifier.unsafe.cs" } }, "end": "(?=;)", @@ -410,19 +413,22 @@ ] }, { - "begin": "(\\b(global)\\b\\s+)?\\b(using)\\b\\s+(\\b(unsafe)\\b\\s+)?(?=(@?[_[:alpha:]][_[:alnum:]]*)\\s*=)", + "begin": "\\b(?:(global)\\s+)?(using)\\b\\s*(?:(unsafe)\\b\\s*)?(@?[_[:alpha:]][_[:alnum:]]*)\\s*(=)", "beginCaptures": { + "1": { + "name": "keyword.other.directive.global.cs" + }, "2": { - "name": "keyword.other.global.cs" + "name": "keyword.other.directive.using.cs" }, "3": { - "name": "keyword.other.using.cs" + "name": "storage.modifier.unsafe.cs" }, - "5": { - "name": "storage.modifier.cs" - }, - "6": { + "4": { "name": "entity.name.type.alias.cs" + }, + "5": { + "name": "keyword.operator.assignment.cs" } }, "end": "(?=;)", @@ -432,20 +438,17 @@ }, { "include": "#type" - }, - { - "include": "#operator-assignment" } ] }, { - "begin": "(\\b(global)\\b\\s+)?\\b(using)\\s*(?!\\(|\\s|var)", + "begin": "\\b(?:(global)\\s+)?(using)\\b\\s*+(?!\\(|var\\b)", "beginCaptures": { - "2": { - "name": "keyword.other.global.cs" + "1": { + "name": "keyword.other.directive.global.cs" }, - "3": { - "name": "keyword.other.using.cs" + "2": { + "name": "keyword.other.directive.using.cs" } }, "end": "(?=;)", @@ -455,7 +458,10 @@ }, { "name": "entity.name.type.namespace.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" + "match": "\\@?[_[:alpha:]][_[:alnum:]]*" + }, + { + "include": "#punctuation-accessor" }, { "include": "#operator-assignment" @@ -551,7 +557,7 @@ "begin": "\\b(namespace)\\s+", "beginCaptures": { "1": { - "name": "keyword.other.namespace.cs" + "name": "storage.type.namespace.cs" } }, "end": "(?<=\\})|(?=;)", @@ -594,7 +600,7 @@ ] }, "storage-modifier": { - "name": "storage.modifier.cs", + "name": "storage.modifier.$1.cs", "match": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", "beginCaptures": { "1": { - "name": "keyword.other.delegate.cs" + "name": "storage.type.delegate.cs" }, "2": { "patterns": [ @@ -712,7 +718,7 @@ "match": "(enum)\\s+(@?[_[:alpha:]][_[:alnum:]]*)", "captures": { "1": { - "name": "keyword.other.enum.cs" + "name": "storage.type.enum.cs" }, "2": { "name": "entity.name.type.enum.cs" @@ -796,7 +802,7 @@ "begin": "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "1": { - "name": "keyword.other.interface.cs" + "name": "storage.type.interface.cs" }, "2": { "name": "entity.name.type.interface.cs" @@ -853,7 +859,7 @@ "begin": "(?x)\n(record)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "1": { - "name": "keyword.other.record.cs" + "name": "storage.type.record.cs" }, "2": { "name": "entity.name.type.class.cs" @@ -913,10 +919,10 @@ "begin": "(?x)\n(\\b(record)\\b\\s+)?\n(struct)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "2": { - "name": "keyword.other.record.cs" + "name": "storage.type.record.cs" }, "3": { - "name": "keyword.other.struct.cs" + "name": "storage.type.struct.cs" }, "4": { "name": "entity.name.type.struct.cs" @@ -984,19 +990,11 @@ "patterns": [ { "match": "\\b(in|out)\\b", - "captures": { - "1": { - "name": "storage.modifier.cs" - } - } + "name": "storage.modifier.$1.cs" }, { "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\b", - "captures": { - "1": { - "name": "entity.name.type.type-parameter.cs" - } - } + "name": "entity.name.type.type-parameter.cs" }, { "include": "#comment" @@ -1033,7 +1031,7 @@ "begin": "(where)\\s+(@?[_[:alpha:]][_[:alnum:]]*)\\s*(:)", "beginCaptures": { "1": { - "name": "keyword.other.where.cs" + "name": "storage.modifier.where.cs" }, "2": { "name": "entity.name.type.type-parameter.cs" @@ -1045,18 +1043,18 @@ "end": "(?=\\{|where|;|=>)", "patterns": [ { - "name": "keyword.other.class.cs", + "name": "storage.type.class.cs", "match": "\\bclass\\b" }, { - "name": "keyword.other.struct.cs", + "name": "storage.type.struct.cs", "match": "\\bstruct\\b" }, { "match": "(new)\\s*(\\()\\s*(\\))", "captures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" }, "2": { "name": "punctuation.parenthesis.open.cs" @@ -1112,7 +1110,7 @@ ] }, "property-declaration": { - "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|$)", + "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|//|/\\*|$)", "beginCaptures": { "1": { "patterns": [ @@ -1144,7 +1142,7 @@ "include": "#property-accessors" }, { - "include": "#expression-body" + "include": "#accessor-getter-expression" }, { "include": "#variable-initializer" @@ -1175,7 +1173,7 @@ ] }, "8": { - "name": "keyword.other.this.cs" + "name": "variable.language.this.cs" } }, "end": "(?<=\\})|(?=;)", @@ -1190,7 +1188,7 @@ "include": "#property-accessors" }, { - "include": "#expression-body" + "include": "#accessor-getter-expression" }, { "include": "#variable-initializer" @@ -1198,10 +1196,10 @@ ] }, "event-declaration": { - "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g(?:\\s*,\\s*\\g)*)\\s*\n(?=\\{|;|$)", + "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(\\g)\\s* # first event name\n(?=\\{|;|,|=|//|/\\*|$)", "beginCaptures": { "1": { - "name": "keyword.other.event.cs" + "name": "storage.type.event.cs" }, "2": { "patterns": [ @@ -1221,15 +1219,7 @@ ] }, "9": { - "patterns": [ - { - "name": "entity.name.variable.event.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-comma" - } - ] + "name": "entity.name.variable.event.cs" } }, "end": "(?<=\\})|(?=;)", @@ -1240,8 +1230,29 @@ { "include": "#event-accessors" }, + { + "name": "entity.name.variable.event.cs", + "match": "@?[_[:alpha:]][_[:alnum:]]*" + }, { "include": "#punctuation-comma" + }, + { + "begin": "=", + "beginCaptures": { + "0": { + "name": "keyword.operator.assignment.cs" + } + }, + "end": "(?<=,)|(?=;)", + "patterns": [ + { + "include": "#expression" + }, + { + "include": "#punctuation-comma" + } + ] } ] }, @@ -1259,22 +1270,6 @@ } }, "patterns": [ - { - "name": "storage.modifier.cs", - "match": "\\b(private|protected|internal)\\b" - }, - { - "name": "keyword.other.get.cs", - "match": "\\b(get)\\b" - }, - { - "name": "keyword.other.set.cs", - "match": "\\b(set)\\b" - }, - { - "name": "keyword.other.init.cs", - "match": "\\b(init)\\b" - }, { "include": "#comment" }, @@ -1282,13 +1277,36 @@ "include": "#attribute-section" }, { - "include": "#expression-body" + "name": "storage.modifier.$1.cs", + "match": "\\b(private|protected|internal)\\b" }, { - "include": "#block" + "begin": "\\b(get)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "storage.type.accessor.$1.cs" + } + }, + "end": "(?<=\\}|;)|(?=\\})", + "patterns": [ + { + "include": "#accessor-getter" + } + ] }, { - "include": "#punctuation-semicolon" + "begin": "\\b(set|init)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "storage.type.accessor.$1.cs" + } + }, + "end": "(?<=\\}|;)|(?=\\})", + "patterns": [ + { + "include": "#accessor-setter" + } + ] } ] }, @@ -1307,24 +1325,114 @@ }, "patterns": [ { - "name": "keyword.other.add.cs", - "match": "\\b(add)\\b" + "include": "#comment" }, { - "name": "keyword.other.remove.cs", - "match": "\\b(remove)\\b" + "include": "#attribute-section" }, { - "include": "#comment" + "begin": "\\b(add|remove)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "storage.type.accessor.$1.cs" + } + }, + "end": "(?<=\\}|;)|(?=\\})", + "patterns": [ + { + "include": "#accessor-setter" + } + ] + } + ] + }, + "accessor-getter": { + "patterns": [ + { + "begin": "\\{", + "beginCaptures": { + "0": { + "name": "punctuation.curlybrace.open.cs" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.curlybrace.close.cs" + } + }, + "contentName": "meta.accessor.getter.cs", + "patterns": [ + { + "include": "#statement" + } + ] }, { - "include": "#attribute-section" + "include": "#accessor-getter-expression" }, { - "include": "#expression-body" + "include": "#punctuation-semicolon" + } + ] + }, + "accessor-getter-expression": { + "begin": "=>", + "beginCaptures": { + "0": { + "name": "keyword.operator.arrow.cs" + } + }, + "end": "(?=;|\\})", + "contentName": "meta.accessor.getter.cs", + "patterns": [ + { + "include": "#ref-modifier" }, { - "include": "#block" + "include": "#expression" + } + ] + }, + "accessor-setter": { + "patterns": [ + { + "begin": "\\{", + "beginCaptures": { + "0": { + "name": "punctuation.curlybrace.open.cs" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.curlybrace.close.cs" + } + }, + "contentName": "meta.accessor.setter.cs", + "patterns": [ + { + "include": "#statement" + } + ] + }, + { + "begin": "=>", + "beginCaptures": { + "0": { + "name": "keyword.operator.arrow.cs" + } + }, + "end": "(?=;|\\})", + "contentName": "meta.accessor.setter.cs", + "patterns": [ + { + "include": "#ref-modifier" + }, + { + "include": "#expression" + } + ] }, { "include": "#punctuation-semicolon" @@ -1425,13 +1533,10 @@ ] }, "constructor-initializer": { - "begin": "\\b(?:(base)|(this))\\b\\s*(?=\\()", + "begin": "\\b(base|this)\\b\\s*(?=\\()", "beginCaptures": { "1": { - "name": "keyword.other.base.cs" - }, - "2": { - "name": "keyword.other.this.cs" + "name": "variable.language.$1.cs" } }, "end": "(?<=\\))", @@ -1468,7 +1573,7 @@ ] }, "operator-declaration": { - "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?(?:\\b(?:operator)))\\s*\n(?(?:\\+|-|\\*|/|%|&|\\||\\^|\\<\\<|\\>\\>|==|!=|\\>|\\<|\\>=|\\<=|!|~|\\+\\+|--|true|false))\\s*\n(?=\\()", + "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n\\b(?operator)\\b\\s*\n(?[+\\-*/%&|\\^!=~<>]+|true|false)\\s*\n(?=\\()", "beginCaptures": { "1": { "patterns": [ @@ -1478,7 +1583,7 @@ ] }, "6": { - "name": "keyword.other.operator-decl.cs" + "name": "storage.type.operator.cs" }, "7": { "name": "entity.name.function.cs" @@ -1509,7 +1614,7 @@ "match": "\\b(explicit)\\b", "captures": { "1": { - "name": "keyword.other.explicit.cs" + "name": "storage.modifier.explicit.cs" } } }, @@ -1517,14 +1622,14 @@ "match": "\\b(implicit)\\b", "captures": { "1": { - "name": "keyword.other.implicit.cs" + "name": "storage.modifier.implicit.cs" } } } ] }, "2": { - "name": "keyword.other.operator-decl.cs" + "name": "storage.type.operator.cs" }, "3": { "patterns": [ @@ -1607,19 +1712,19 @@ "begin": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\b\\s*", + "switch-expression": { + "begin": "\\{", "beginCaptures": { - "1": { + "0": { + "name": "punctuation.curlybrace.open.cs" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.curlybrace.close.cs" + } + }, + "patterns": [ + { + "include": "#punctuation-comma" + }, + { + "begin": "=>", + "beginCaptures": { + "0": { + "name": "keyword.operator.arrow.cs" + } + }, + "end": "(?=,|})", "patterns": [ { - "include": "#type" + "include": "#expression" } ] }, - "2": { - "name": "entity.name.variable.local.cs" + { + "begin": "\\b(when)\\b", + "beginCaptures": { + "1": { + "name": "keyword.control.conditional.when.cs" + } + }, + "end": "(?==>|,|})", + "patterns": [ + { + "include": "#case-guard" + } + ] + }, + { + "begin": "(?!\\s)", + "end": "(?=\\bwhen\\b|=>|,|})", + "patterns": [ + { + "include": "#pattern" + } + ] + } + ] + }, + "case-guard": { + "patterns": [ + { + "include": "#parenthesized-expression" + }, + { + "include": "#expression" + } + ] + }, + "is-expression": { + "begin": "(?)", + "end": "(?=[)}\\],;:?=&|^]|!=)", "patterns": [ { - "include": "#comment" + "include": "#pattern" + } + ] + }, + "pattern": { + "patterns": [ + { + "include": "#intrusive" + }, + { + "include": "#combinator-pattern" + }, + { + "include": "#discard-pattern" + }, + { + "include": "#constant-pattern" + }, + { + "include": "#relational-pattern" + }, + { + "include": "#var-pattern" + }, + { + "include": "#type-pattern" + }, + { + "include": "#positional-pattern" + }, + { + "include": "#property-pattern" }, { - "include": "#switch-when-clause" + "include": "#list-pattern" + }, + { + "include": "#slice-pattern" } ] }, - "switch-property-expression": { - "begin": "(?x) # e.g. int x OR var x\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(\\{)", + "combinator-pattern": { + "match": "\\b(and|or|not)\\b", + "name": "keyword.operator.expression.pattern.combinator.$1.cs" + }, + "discard-pattern": { + "match": "_(?![_[:alnum:]])", + "name": "variable.language.discard.cs" + }, + "constant-pattern": { + "patterns": [ + { + "include": "#boolean-literal" + }, + { + "include": "#null-literal" + }, + { + "include": "#numeric-literal" + }, + { + "include": "#char-literal" + }, + { + "include": "#string-literal" + }, + { + "include": "#raw-string-literal" + }, + { + "include": "#verbatim-string-literal" + }, + { + "include": "#type-operator-expression" + }, + { + "include": "#expression-operator-expression" + }, + { + "include": "#expression-operators" + }, + { + "include": "#casted-constant-pattern" + } + ] + }, + "casted-constant-pattern": { + "begin": "(?x)\n(\\()\n ([\\s.:@_[:alnum:]]+)\n(\\))\n(?=[\\s+\\-!~]*@?[_[:alnum:]('\"]+)", "beginCaptures": { "1": { + "name": "punctuation.parenthesis.open.cs" + }, + "2": { "patterns": [ { - "include": "#type" + "include": "#type-builtin" + }, + { + "include": "#type-name" } ] }, - "6": { - "name": "punctuation.curlybrace.open.cs" + "3": { + "name": "punctuation.parenthesis.close.cs" } }, - "end": "\\}", - "endCaptures": { + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#casted-constant-pattern" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.parenthesis.open.cs" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.parenthesis.close.cs" + } + }, + "patterns": [ + { + "include": "#constant-pattern" + } + ] + }, + { + "include": "#constant-pattern" + }, + { + "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(\\:\\:)", + "captures": { + "1": { + "name": "entity.name.type.alias.cs" + }, + "2": { + "name": "punctuation.separator.coloncolon.cs" + } + } + }, + { + "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(\\.)", + "captures": { + "1": { + "name": "entity.name.type.cs" + }, + "2": { + "name": "punctuation.accessor.cs" + } + } + }, + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "variable.other.constant.cs" + } + ] + }, + "relational-pattern": { + "begin": "<=?|>=?", + "beginCaptures": { "0": { - "name": "punctuation.curlybrace.close.cs" + "name": "keyword.operator.relational.cs" } }, + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", "patterns": [ { "include": "#expression" - }, - { - "include": "#punctuation-comma" } ] }, - "switch-var-pattern": { - "begin": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*", + "var-pattern": { + "begin": "\\b(var)\\b", "beginCaptures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" + } + }, + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#designation-pattern" + } + ] + }, + "designation-pattern": { + "patterns": [ + { + "include": "#intrusive" }, - "2": { + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.parenthesis.open.cs" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.parenthesis.close.cs" + } + }, "patterns": [ { - "include": "#tuple-declaration-deconstruction-element-list" + "include": "#punctuation-comma" + }, + { + "include": "#designation-pattern" } ] + }, + { + "include": "#simple-designation-pattern" } - }, - "end": "(?==>)", + ] + }, + "simple-designation-pattern": { "patterns": [ { - "include": "#comment" + "include": "#discard-pattern" }, { - "include": "#switch-when-clause" + "match": "@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.variable.local.cs" } ] }, - "switch-when-clause": { - "begin": "(?)", + ] + }, + "type-subpattern": { "patterns": [ { - "include": "#comment" + "include": "#type-builtin" }, { - "include": "#expression" + "begin": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(::)", + "beginCaptures": { + "1": { + "name": "entity.name.type.alias.cs" + }, + "2": { + "name": "punctuation.separator.coloncolon.cs" + } + }, + "end": "(?<=[_[:alnum:]])|(?=[.<\\[\\({)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#intrusive" + }, + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.type.cs" + } + ] }, { - "include": "#punctuation-comma" + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.type.cs" }, { - "match": "\\(", - "captures": { + "begin": "\\.", + "beginCaptures": { "0": { - "name": "punctuation.parenthesis.open.cs" + "name": "punctuation.accessor.cs" } - } + }, + "end": "(?<=[_[:alnum:]])|(?=[<\\[\\({)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#intrusive" + }, + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.type.cs" + } + ] }, { - "match": "\\)", - "captures": { + "include": "#type-arguments" + }, + { + "include": "#type-array-suffix" + }, + { + "match": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2176,7 +2719,7 @@ "match": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)?\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s+\n\\b(in)\\b", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2194,9 +2737,6 @@ "include": "#expression" } ] - }, - { - "include": "#statement" } ] }, @@ -2217,7 +2757,7 @@ "begin": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", + "begin": "(?x)\n(?:\n (?:(\\bref)\\s+(?:(\\breadonly)\\s+)?)?(\\bvar\\b)| # ref local\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*[?*]\\s*)? # nullable or pointer suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", "beginCaptures": { "1": { - "name": "keyword.other.using.cs" + "name": "storage.modifier.ref.cs" }, "2": { - "name": "storage.modifier.cs" + "name": "storage.modifier.readonly.cs" }, "3": { - "name": "storage.modifier.cs" + "name": "storage.type.var.cs" }, "4": { - "name": "keyword.other.var.cs" - }, - "5": { "patterns": [ { "include": "#type" } ] }, - "10": { + "9": { "name": "entity.name.variable.local.cs" } }, - "end": "(?=;|\\))", + "end": "(?=[;)}])", "patterns": [ { "name": "entity.name.variable.local.cs", @@ -2486,7 +3068,7 @@ "begin": "(?x)\n(?\\b(?:const)\\b)\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(?=,|;|=)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "name": "storage.modifier.const.cs" }, "2": { "patterns": [ @@ -2517,9 +3099,49 @@ ] }, "local-function-declaration": { + "begin": "(?x)\n\\b((?:(?:async|unsafe|static|extern)\\s+)*)\n(?\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n \\s*(?:,\\s*)* # commata for multi-dimensional arrays\n \\]\n (?:\\s*\\?)? # arrays can be nullable reference types\n )*\n)\\s+\n(\\g)\\s*\n(<[^<>]+>)?\\s*\n(?=\\()", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#storage-modifier" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type" + } + ] + }, + "7": { + "name": "entity.name.function.cs" + }, + "8": { + "patterns": [ + { + "include": "#type-parameter-list" + } + ] + } + }, + "end": "(?<=\\})|(?=;)", "patterns": [ { - "include": "#method-declaration" + "include": "#comment" + }, + { + "include": "#parenthesized-parameter-list" + }, + { + "include": "#generic-constraints" + }, + { + "include": "#expression-body" + }, + { + "include": "#block" } ] }, @@ -2527,7 +3149,7 @@ "begin": "(?x) # e.g. var (x, y) = GetPoint();\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*\n(?=;|=|\\))", "beginCaptures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2635,7 +3257,7 @@ "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)\\]])", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2653,7 +3275,7 @@ "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2667,16 +3289,13 @@ } } }, - "checked-unchecked-expression": { - "begin": "(?>>?|\\|)?=(?!=|>)", + "beginCaptures": { + "0": { + "patterns": [ + { + "include": "#assignment-operators" + } + ] + } + }, + "end": "(?=[,\\)\\];}])", + "patterns": [ + { + "include": "#ref-modifier" + }, + { + "include": "#expression" + } + ] + }, + "assignment-operators": { "patterns": [ { "name": "keyword.operator.assignment.compound.cs", @@ -3391,11 +4006,19 @@ }, { "name": "keyword.operator.assignment.compound.bitwise.cs", - "match": "\\&=|\\^=|<<=|>>=|\\|=" + "match": "\\&=|\\^=|<<=|>>>?=|\\|=" }, + { + "name": "keyword.operator.assignment.cs", + "match": "\\=" + } + ] + }, + "expression-operators": { + "patterns": [ { "name": "keyword.operator.bitwise.shift.cs", - "match": "<<|>>" + "match": "<<|>>>?" }, { "name": "keyword.operator.comparison.cs", @@ -3413,10 +4036,6 @@ "name": "keyword.operator.bitwise.cs", "match": "\\&|~|\\^|\\|" }, - { - "name": "keyword.operator.assignment.cs", - "match": "\\=" - }, { "name": "keyword.operator.decrement.cs", "match": "--" @@ -3427,56 +4046,50 @@ }, { "name": "keyword.operator.arithmetic.cs", - "match": "%|\\*|/|-|\\+" + "match": "\\+|-(?!>)|\\*|/|%" }, { "name": "keyword.operator.null-coalescing.cs", "match": "\\?\\?" + }, + { + "name": "keyword.operator.range.cs", + "match": "\\.\\." } ] }, - "switch-literal": { - "name": "constant.language.null.cs", - "match": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", - "captures": { - "1": { - "name": "keyword.other.as.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - } - } - }, - "is-expression": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?(?!\\?))? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n \\s*(?:,\\s*)* # commata for multi-dimensional arrays\n \\]\n (?:\\s*\\?(?!\\?))? # arrays can be nullable reference types\n )*\n )\n)?", "captures": { "1": { - "name": "keyword.other.is.cs" + "name": "keyword.operator.expression.as.cs" }, "2": { "patterns": [ @@ -3571,19 +4169,20 @@ } } }, - "this-or-base-expression": { - "match": "\\b(?:(base)|(this))\\b", - "captures": { - "1": { - "name": "keyword.other.base.cs" + "language-variable": { + "patterns": [ + { + "name": "variable.language.$1.cs", + "match": "\\b(base|this)\\b" }, - "2": { - "name": "keyword.other.this.cs" + { + "name": "variable.other.$1.cs", + "match": "\\b(value)\\b" } - } + ] }, "invocation-expression": { - "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(?\\s*<([^<>]|\\g)+>\\s*)?\\s* # type arguments\n(?=\\() # open paren of argument list", + "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(\n <\n (?\n [^<>()]+|\n <\\g+>|\n \\(\\g+\\)\n )+\n >\\s*\n)? # type arguments\n(?=\\() # open paren of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -3592,9 +4191,12 @@ "name": "punctuation.accessor.cs" }, "3": { - "name": "entity.name.function.cs" + "name": "punctuation.accessor.pointer.cs" }, "4": { + "name": "entity.name.function.cs" + }, + "5": { "patterns": [ { "include": "#type-arguments" @@ -3610,7 +4212,7 @@ ] }, "element-access-expression": { - "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(?:(@?[_[:alpha:]][_[:alnum:]]*)\\s*)? # property name\n(?:(\\?)\\s*)? # null-conditional operator?\n(?=\\[) # open bracket of argument list", + "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(?:(@?[_[:alpha:]][_[:alnum:]]*)\\s*)? # property name\n(?:(\\?)\\s*)? # null-conditional operator?\n(?=\\[) # open bracket of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -3619,9 +4221,12 @@ "name": "punctuation.accessor.cs" }, "3": { - "name": "variable.other.object.property.cs" + "name": "punctuation.accessor.pointer.cs" }, "4": { + "name": "variable.other.object.property.cs" + }, + "5": { "name": "keyword.operator.null-conditional.cs" } }, @@ -3635,7 +4240,7 @@ "member-access-expression": { "patterns": [ { - "match": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(\\.)\\s* # preceding dot\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # property name\n(?![_[:alnum:]]|\\(|(\\?)?\\[|<) # next character is not alpha-numeric, nor a (, [, or <. Also, test for ?[", + "match": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # property name\n(?![_[:alnum:]]|\\(|(\\?)?\\[|<) # next character is not alpha-numeric, nor a (, [, or <. Also, test for ?[", "captures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -3644,6 +4249,9 @@ "name": "punctuation.accessor.cs" }, "3": { + "name": "punctuation.accessor.pointer.cs" + }, + "4": { "name": "variable.other.object.property.cs" } } @@ -3667,7 +4275,7 @@ } }, { - "match": "(?x)\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?=\n (\\s*\\?)?\n \\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*\n)", + "match": "(?x)\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?=\n \\s*(?:(?:\\?\\s*)?\\.|->)\n \\s*@?[_[:alpha:]][_[:alnum:]]*\n)", "captures": { "1": { "name": "variable.other.object.cs" @@ -3690,7 +4298,7 @@ "begin": "(?x)\n(new)(?:\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n))?\\s*\n(?=\\()", "beginCaptures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" }, "2": { "patterns": [ @@ -3708,10 +4316,10 @@ ] }, "object-creation-expression-with-no-parameters": { - "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|$)", + "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|//|/\\*|$)", "captures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" }, "2": { "patterns": [ @@ -3726,7 +4334,7 @@ "begin": "(?x)\n\\b(new|stackalloc)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(?=\\[)", "beginCaptures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.$1.cs" }, "2": { "patterns": [ @@ -3744,14 +4352,17 @@ ] }, "anonymous-object-creation-expression": { - "begin": "\\b(new)\\b\\s*(?=\\{|$)", + "begin": "\\b(new)\\b\\s*(?=\\{|//|/\\*|$)", "beginCaptures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" } }, "end": "(?<=\\})", "patterns": [ + { + "include": "#comment" + }, { "include": "#initializer-expression" } @@ -3829,7 +4440,7 @@ "match": "(?x)\n(?:(?:\\b(ref|params|out|in|this)\\b)\\s+)?\n(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)", "captures": { "1": { - "name": "storage.modifier.cs" + "name": "storage.modifier.$1.cs" }, "2": { "patterns": [ @@ -3913,11 +4524,25 @@ "argument": { "patterns": [ { - "name": "storage.modifier.cs", - "match": "\\b(ref|out|in)\\b" + "name": "storage.modifier.$1.cs", + "match": "\\b(ref|in)\\b" }, { - "include": "#declaration-expression-local" + "begin": "\\b(out)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.out.cs" + } + }, + "end": "(?=,|\\)|\\])", + "patterns": [ + { + "include": "#declaration-expression-local" + }, + { + "include": "#expression" + } + ] }, { "include": "#expression" @@ -3928,7 +4553,7 @@ "begin": "(?x)\n\\b(from)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.from.cs" + "name": "keyword.operator.expression.query.from.cs" }, "2": { "patterns": [ @@ -3941,7 +4566,7 @@ "name": "entity.name.variable.range-variable.cs" }, "8": { - "name": "keyword.query.in.cs" + "name": "keyword.operator.expression.query.in.cs" } }, "end": "(?=;|\\))", @@ -3980,7 +4605,7 @@ "begin": "(?x)\n\\b(let)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=)\\s*", "beginCaptures": { "1": { - "name": "keyword.query.let.cs" + "name": "keyword.operator.expression.query.let.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4003,7 +4628,7 @@ "begin": "(?x)\n\\b(where)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.where.cs" + "name": "keyword.operator.expression.query.where.cs" } }, "end": "(?=;|\\))", @@ -4020,7 +4645,7 @@ "begin": "(?x)\n\\b(join)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.join.cs" + "name": "keyword.operator.expression.query.join.cs" }, "2": { "patterns": [ @@ -4033,7 +4658,7 @@ "name": "entity.name.variable.range-variable.cs" }, "8": { - "name": "keyword.query.in.cs" + "name": "keyword.operator.expression.query.in.cs" } }, "end": "(?=;|\\))", @@ -4059,7 +4684,7 @@ "match": "\\b(on)\\b\\s*", "captures": { "1": { - "name": "keyword.query.on.cs" + "name": "keyword.operator.expression.query.on.cs" } } }, @@ -4067,7 +4692,7 @@ "match": "\\b(equals)\\b\\s*", "captures": { "1": { - "name": "keyword.query.equals.cs" + "name": "keyword.operator.expression.query.equals.cs" } } }, @@ -4075,7 +4700,7 @@ "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", "captures": { "1": { - "name": "keyword.query.into.cs" + "name": "keyword.operator.expression.query.into.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4086,7 +4711,7 @@ "begin": "\\b(orderby)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.orderby.cs" + "name": "keyword.operator.expression.query.orderby.cs" } }, "end": "(?=;|\\))", @@ -4106,13 +4731,10 @@ ] }, "ordering-direction": { - "match": "\\b(?:(ascending)|(descending))\\b", + "match": "\\b(ascending|descending)\\b", "captures": { "1": { - "name": "keyword.query.ascending.cs" - }, - "2": { - "name": "keyword.query.descending.cs" + "name": "keyword.operator.expression.query.$1.cs" } } }, @@ -4120,7 +4742,7 @@ "begin": "\\b(select)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.select.cs" + "name": "keyword.operator.expression.query.select.cs" } }, "end": "(?=;|\\))", @@ -4137,7 +4759,7 @@ "begin": "\\b(group)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.group.cs" + "name": "keyword.operator.expression.query.group.cs" } }, "end": "(?=;|\\))", @@ -4160,7 +4782,7 @@ "match": "\\b(by)\\b\\s*", "captures": { "1": { - "name": "keyword.query.by.cs" + "name": "keyword.operator.expression.query.by.cs" } } }, @@ -4168,7 +4790,7 @@ "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", "captures": { "1": { - "name": "keyword.query.into.cs" + "name": "keyword.operator.expression.query.into.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4178,10 +4800,15 @@ "anonymous-method-expression": { "patterns": [ { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=>)", + "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=>)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "patterns": [ + { + "match": "async|static", + "name": "storage.modifier.$0.cs" + } + ] }, "2": { "name": "entity.name.variable.parameter.cs" @@ -4204,10 +4831,15 @@ ] }, { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(\\(.*?\\))\\s*\n(=>)", + "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n(?\n \\(\n (?:[^()]|\\g)*\n \\)\n)\\s*\n(=>)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "patterns": [ + { + "match": "async|static", + "name": "storage.modifier.$0.cs" + } + ] }, "2": { "patterns": [ @@ -4234,13 +4866,18 @@ ] }, { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(?:\\b(delegate)\\b\\s*)", + "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n(?:\\b(delegate)\\b\\s*)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "patterns": [ + { + "match": "async|static", + "name": "storage.modifier.$0.cs" + } + ] }, "2": { - "name": "keyword.other.delegate.cs" + "name": "storage.type.delegate.cs" } }, "end": "(?=\\)|;|}|,)", @@ -4250,9 +4887,6 @@ }, { "include": "#block" - }, - { - "include": "#expression" } ] } @@ -4290,7 +4924,7 @@ "match": "(?x)\n(?:\\b(ref|out|in)\\b)?\\s*\n(?:(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+)?\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { - "name": "storage.modifier.cs" + "name": "storage.modifier.$1.cs" }, "2": { "patterns": [ @@ -4305,7 +4939,6 @@ } }, "type": { - "name": "meta.type.cs", "patterns": [ { "include": "#comment" @@ -4333,16 +4966,19 @@ }, { "include": "#type-nullable-suffix" + }, + { + "include": "#type-pointer-suffix" } ] }, "ref-modifier": { - "name": "storage.modifier.cs", - "match": "\\b(ref)\\b" + "name": "storage.modifier.ref.cs", + "match": "\\bref\\b" }, "readonly-modifier": { - "name": "storage.modifier.cs", - "match": "\\b(readonly)\\b" + "name": "storage.modifier.readonly.cs", + "match": "\\breadonly\\b" }, "tuple-type": { "begin": "\\(", @@ -4382,10 +5018,10 @@ } }, "type-builtin": { - "match": "\\b(bool|byte|char|decimal|double|float|int|long|object|sbyte|short|string|uint|ulong|ushort|void|dynamic)\\b", + "match": "\\b(bool|s?byte|u?short|n?u?int|u?long|float|double|decimal|char|string|object|void|dynamic)\\b", "captures": { "1": { - "name": "keyword.type.cs" + "name": "keyword.type.$1.cs" } } }, @@ -4444,9 +5080,6 @@ } }, "patterns": [ - { - "include": "#comment" - }, { "include": "#type" }, @@ -4469,6 +5102,9 @@ } }, "patterns": [ + { + "include": "#intrusive" + }, { "include": "#punctuation-comma" } @@ -4476,11 +5112,11 @@ }, "type-nullable-suffix": { "match": "\\?", - "captures": { - "0": { - "name": "punctuation.separator.question-mark.cs" - } - } + "name": "punctuation.separator.question-mark.cs" + }, + "type-pointer-suffix": { + "match": "\\*", + "name": "punctuation.separator.asterisk.cs" }, "operator-assignment": { "name": "keyword.operator.assignment.cs", @@ -4498,6 +5134,16 @@ "name": "punctuation.accessor.cs", "match": "\\." }, + "intrusive": { + "patterns": [ + { + "include": "#preprocessor" + }, + { + "include": "#comment" + } + ] + }, "preprocessor": { "name": "meta.preprocessor.cs", "begin": "^\\s*(\\#)\\s*", @@ -4803,38 +5449,47 @@ "comment": { "patterns": [ { - "name": "comment.block.cs", - "begin": "/\\*", - "beginCaptures": { - "0": { + "name": "comment.block.documentation.cs", + "begin": "(^\\s+)?(///)(?!/)", + "while": "^(\\s*)(///)(?!/)", + "captures": { + "1": { + "name": "punctuation.whitespace.comment.leading.cs" + }, + "2": { "name": "punctuation.definition.comment.cs" } }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.cs" + "patterns": [ + { + "include": "#xml-doc-comment" } - } + ] }, { - "begin": "(^\\s+)?(?=//)", - "beginCaptures": { + "name": "comment.block.documentation.cs", + "begin": "(^\\s+)?(/\\*\\*)(?!/)", + "end": "(^\\s+)?(\\*/)", + "captures": { "1": { "name": "punctuation.whitespace.comment.leading.cs" + }, + "2": { + "name": "punctuation.definition.comment.cs" } }, - "end": "(?=$)", "patterns": [ { - "name": "comment.block.documentation.cs", - "begin": "(?((prev, selection) => + const rangesToUpdate = editor.selections + .reduceRight((prev, selection) => prev.concat(getRangesToUpdate(document, selection, rootNode)), []); if (!rangesToUpdate.length) { return; diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 422fce5e7f625..a18aed6e955e2 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -54,7 +54,7 @@ "%html.completion.attributeDefaultValue.empty%" ], "default": "doublequotes", - "description": "%html.completion.attributeDefaultValue%" + "markdownDescription": "%html.completion.attributeDefaultValue%" }, "html.customData": { "type": "array", diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index acb6474d63e4d..f36ecf34f023f 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -21,7 +21,7 @@ "html.format.wrapAttributes.preservealigned": "Preserve wrapping of attributes but align.", "html.format.templating.desc": "Honor django, erb, handlebars and php templating language tags.", "html.format.unformattedContentDelimiter.desc": "Keep text content together between this string.", - "html.format.wrapAttributesIndentSize.desc": "Indent wrapped attributes to after N characters. Use `null` to use the default indent size. Ignored if `#html.format.wrapAttributes#` is set to 'aligned'.", + "html.format.wrapAttributesIndentSize.desc": "Indent wrapped attributes to after N characters. Use `null` to use the default indent size. Ignored if `#html.format.wrapAttributes#` is set to `aligned`.", "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", diff --git a/extensions/java/cgmanifest.json b/extensions/java/cgmanifest.json index be9e7439fe763..72e0c5c882b3e 100644 --- a/extensions/java/cgmanifest.json +++ b/extensions/java/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "redhat-developer/vscode-java", "repositoryUrl": "https://github.com/redhat-developer/vscode-java", - "commitHash": "5fb57e8e1c5d776b21be13cd7227b25b87edf4a6" + "commitHash": "5d224a552cf5f0f8ebccf69e43e2575ed2c13839" } }, "license": "MIT", @@ -44,7 +44,7 @@ "suitability for any purpose." ], "description": "This grammar was derived from https://github.com/atom/language-java/blob/master/grammars/java.cson.", - "version": "1.21.0" + "version": "1.22.0" } ], "version": 1 diff --git a/extensions/java/syntaxes/java.tmLanguage.json b/extensions/java/syntaxes/java.tmLanguage.json index 337545b0233ec..7f969ccb78779 100644 --- a/extensions/java/syntaxes/java.tmLanguage.json +++ b/extensions/java/syntaxes/java.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/redhat-developer/vscode-java/commit/5fb57e8e1c5d776b21be13cd7227b25b87edf4a6", + "version": "https://github.com/redhat-developer/vscode-java/commit/5d224a552cf5f0f8ebccf69e43e2575ed2c13839", "name": "Java", "scopeName": "source.java", "patterns": [ diff --git a/extensions/julia/cgmanifest.json b/extensions/julia/cgmanifest.json index 0dac126a5ae46..d546ac2db12de 100644 --- a/extensions/julia/cgmanifest.json +++ b/extensions/julia/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "JuliaEditorSupport/atom-language-julia", "repositoryUrl": "https://github.com/JuliaEditorSupport/atom-language-julia", - "commitHash": "ccc0277c9ee9af34a0b50e5fa27a6f5191601b8c" + "commitHash": "7cbe6a7c4f2c8275e15f5b6e0722d285730ffb99" } }, "license": "MIT", diff --git a/extensions/julia/syntaxes/julia.tmLanguage.json b/extensions/julia/syntaxes/julia.tmLanguage.json index c4e146ee5e792..66998399c4b9f 100644 --- a/extensions/julia/syntaxes/julia.tmLanguage.json +++ b/extensions/julia/syntaxes/julia.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/JuliaEditorSupport/atom-language-julia/commit/ccc0277c9ee9af34a0b50e5fa27a6f5191601b8c", + "version": "https://github.com/JuliaEditorSupport/atom-language-julia/commit/7cbe6a7c4f2c8275e15f5b6e0722d285730ffb99", "name": "Julia", "scopeName": "source.julia", "comment": "This grammar is used by Atom (Oniguruma), GitHub (PCRE), and VSCode (Oniguruma),\nso all regexps must be compatible with both engines.\n\nSpecs:\n- https://github.com/kkos/oniguruma/blob/master/doc/RE\n- https://www.pcre.org/current/doc/html/", @@ -356,13 +356,16 @@ "name": "keyword.operator.shift.julia" }, { - "match": "(?:\\s*(::|>:|<:)\\s*((?:(?:Union)?\\([^)]*\\)|[[:alpha:]_$∇][[:word:]⁺-ₜ!′\\.]*(?:(?:{(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})|(?:\".+?(?:|<:)\\s*((?:(?:Union)?\\([^)]*\\)|[[:alpha:]_$∇][[:word:]⁺-ₜ!′\\.]*(?:(?:{(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})|(?:\".+?(?\\?\\[\\]\\^\\_\\`\\|]+)", + "begin": "((@)(?i:preamble))\\s*(\\()\\s*", + "beginCaptures": { + "1": { + "name": "keyword.other.preamble.bibtex" + }, + "2": { + "name": "punctuation.definition.keyword.bibtex" + }, + "3": { + "name": "punctuation.section.preamble.begin.bibtex" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.section.preamble.end.bibtex" + } + }, + "name": "meta.preamble.parenthesis.bibtex", + "patterns": [ + { + "include": "#field_value" + } + ] + }, + { + "begin": "((@)(?i:string))\\s*(\\{)\\s*([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)", "beginCaptures": { "1": { "name": "keyword.other.string-constant.bibtex" @@ -51,12 +95,12 @@ "name": "meta.string-constant.braces.bibtex", "patterns": [ { - "include": "#string_content" + "include": "#field_value" } ] }, { - "begin": "((@)(?i:string))\\s*(\\()\\s*([a-zA-Z0-9\\!\\$\\&\\*\\+\\-\\.\\/\\:\\;\\<\\>\\?\\[\\]\\^\\_\\`\\|]+)", + "begin": "((@)(?i:string))\\s*(\\()\\s*([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)", "beginCaptures": { "1": { "name": "keyword.other.string-constant.bibtex" @@ -80,12 +124,12 @@ "name": "meta.string-constant.parenthesis.bibtex", "patterns": [ { - "include": "#string_content" + "include": "#field_value" } ] }, { - "begin": "((@)[a-zA-Z]+)\\s*(\\{)\\s*([^\\s,]*)", + "begin": "((@)[a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\{)\\s*([^\\s,}]*)", "beginCaptures": { "1": { "name": "keyword.other.entry-type.bibtex" @@ -109,13 +153,7 @@ "name": "meta.entry.braces.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#url_field" - }, - { - "begin": "([a-zA-Z0-9\\!\\$\\&\\*\\+\\-\\.\\/\\:\\;\\<\\>\\?\\[\\]\\^\\_\\`\\|]+)\\s*(\\=)", + "begin": "([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\=)", "beginCaptures": { "1": { "name": "support.function.key.bibtex" @@ -128,23 +166,14 @@ "name": "meta.key-assignment.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#integer" - }, - { - "include": "#string_content" - }, - { - "include": "#string_var" + "include": "#field_value" } ] } ] }, { - "begin": "((@)[a-zA-Z]+)\\s*(\\()\\s*([^\\s,]*)", + "begin": "((@)[a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\()\\s*([^\\s,]*)", "beginCaptures": { "1": { "name": "keyword.other.entry-type.bibtex" @@ -168,13 +197,7 @@ "name": "meta.entry.parenthesis.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#url_field" - }, - { - "begin": "([a-zA-Z0-9\\!\\$\\&\\*\\+\\-\\.\\/\\:\\;\\<\\>\\?\\[\\]\\^\\_\\`\\|]+)\\s*(\\=)", + "begin": "([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\=)", "beginCaptures": { "1": { "name": "support.function.key.bibtex" @@ -187,16 +210,7 @@ "name": "meta.key-assignment.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#integer" - }, - { - "include": "#string_content" - }, - { - "include": "#string_var" + "include": "#field_value" } ] } @@ -209,6 +223,23 @@ } ], "repository": { + "field_value": { + "patterns": [ + { + "include": "#string_content" + }, + { + "include": "#integer" + }, + { + "include": "#string_var" + }, + { + "name": "keyword.operator.bibtex", + "match": "#" + } + ] + }, "integer": { "match": "\\s*(\\d+)\\s*", "captures": { @@ -218,13 +249,13 @@ } }, "nested_braces": { - "begin": "(?\\?\\[\\]\\^\\_\\`\\|]+)\\s*(#)?", + "match": "[a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*", "captures": { - "1": { - "name": "keyword.operator.bibtex" - }, - "2": { + "0": { "name": "support.variable.bibtex" - }, - "3": { - "name": "keyword.operator.bibtex" } } }, @@ -259,23 +284,13 @@ "name": "punctuation.definition.string.begin.bibtex" } }, - "end": "(\\})(?=(?:,?\\s*\\}?\\s*\\n)|(?:\\s*#))", + "end": "\\}", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.string.end.bibtex" } }, "patterns": [ - { - "include": "#url_cmd" - }, - { - "include": "#percentage_comment" - }, - { - "match": "@", - "name": "invalid.illegal.at-sign.bibtex" - }, { "include": "#nested_braces" } @@ -288,7 +303,7 @@ "name": "punctuation.definition.string.begin.bibtex" } }, - "end": "\"(?=(?:,?\\s*\\}?\\s*\\n)|(?:\\s*#))", + "end": "\"", "endCaptures": { "0": { "name": "punctuation.definition.string.end.bibtex" @@ -296,106 +311,11 @@ }, "patterns": [ { - "include": "#url_cmd" - }, - { - "include": "#percentage_comment" - }, - { - "match": "@", - "name": "invalid.illegal.at-sign.bibtex" - } - ] - } - ] - }, - "string_url": { - "patterns": [ - { - "begin": "\\{|\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.bibtex" - } - }, - "end": "(\\}|\")(?=(?:,?\\s*\\}?\\s*\\n)|(?:\\s*#))", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.bibtex" - } - }, - "contentName": "meta.url.bibtex", - "patterns": [ - { - "include": "#url_cmd" - } - ] - } - ] - }, - "percentage_comment": { - "patterns": [ - { - "begin": "(^[ \\t]+)?(?=%)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.bibtex" - } - }, - "end": "(?!\\G)", - "patterns": [ - { - "begin": "(? { + // This same code is also inlined at `src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html` + if (!crypto.subtle) { + throw new Error(`'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).`); + } + + const strData = JSON.stringify({ parentOrigin, salt }); + const encoder = new TextEncoder(); + const arrData = encoder.encode(strData); + const hash = await crypto.subtle.digest('sha-256', arrData); + return sha256AsBase32(hash); +} + +function sha256AsBase32(bytes: ArrayBuffer): string { + const array = Array.from(new Uint8Array(bytes)); + const hexArray = array.map(b => b.toString(16).padStart(2, '0')).join(''); + // sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32 + return BigInt(`0x${hexArray}`).toString(32).padStart(52, '0'); +} diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index dad5135fd5664..8ce413f6e70f9 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -182,7 +182,7 @@ export class Throttler implements IDisposable { queue(promiseFactory: ITask>): Promise { if (this.isDisposed) { - throw new Error('Throttler is disposed'); + return Promise.reject(new Error('Throttler is disposed')); } if (this.activePromise) { diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 31e345a0bf2c4..966b07b191c7c 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -58,6 +58,7 @@ export interface IObservable { * (see {@link ConvenientObservable.map}). */ map(fn: (value: T, reader: IReader) => TNew): IObservable; + map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; /** * A human-readable name for debugging purposes. @@ -165,9 +166,15 @@ export abstract class ConvenientObservable implements IObservable(fn: (value: T, reader: IReader) => TNew): IObservable { + public map(fn: (value: T, reader: IReader) => TNew): IObservable; + public map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; + public map(fnOrOwner: object | ((value: T, reader: IReader) => TNew), fnOrUndefined?: (value: T, reader: IReader) => TNew): IObservable { + const owner = fnOrUndefined === undefined ? undefined : fnOrOwner as object; + const fn = fnOrUndefined === undefined ? fnOrOwner as (value: T, reader: IReader) => TNew : fnOrUndefined; + return _derived( { + owner, debugName: () => { const name = getFunctionName(fn); if (name !== undefined) { @@ -180,7 +187,10 @@ export abstract class ConvenientObservable implements IObservable fn(this.read(reader), reader), diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 0f1abf6043ec9..53c2a3a7eeed0 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -26,7 +26,7 @@ export function derived(computeFnOrOwner: ((reader: IReader) => T) | object, export function derivedOpts( options: { owner?: object; - debugName?: string | (() => string); + debugName?: string | (() => string | undefined); equalityComparer?: EqualityComparer; }, computeFn: (reader: IReader) => T diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 932344873e981..4cb9eceb360ae 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -188,9 +188,14 @@ suite('Async', () => { const promises: Promise[] = []; throttler.dispose(); - assert.throws(() => promises.push(throttler.queue(factory))); - assert.strictEqual(factoryCalls, 0); - await Promise.all(promises); + promises.push(throttler.queue(factory)); + + try { + await Promise.all(promises); + assert.fail('should fail'); + } catch (err) { + assert.strictEqual(factoryCalls, 0); + } }); }); diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 343167bd57dbb..ae8627eb117f8 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -231,7 +231,7 @@ suite('No Leakage Utilities', () => { eventEmitter.event(() => { // noop }); - }); + }, false); }, e => e.message.indexOf('undisposed disposables') !== -1); }); @@ -239,7 +239,7 @@ suite('No Leakage Utilities', () => { assertThrows(() => { throwIfDisposablesAreLeaked(() => { new DisposableStore(); - }); + }, false); }, e => e.message.indexOf('undisposed disposables') !== -1); }); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index be85a00daffd4..9c38a6a06df93 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -109,7 +109,7 @@ export class DisposableTracker implements IDisposableTracker { return leaking; } - ensureNoLeakingDisposables() { + ensureNoLeakingDisposables(logToConsole = true) { const rootParentCache = new Map(); const leakingObjects = [...this.livingDisposables.values()] @@ -184,7 +184,9 @@ export class DisposableTracker implements IDisposableTracker { message += `\n\n\n... and ${uncoveredLeakingObjs.length - maxReported} more leaking disposables\n\n`; } - console.error(message); + if (logToConsole) { + console.error(message); + } throw new Error(`There are ${uncoveredLeakingObjs.length} undisposed disposables!${message}`); } @@ -225,12 +227,12 @@ export function ensureNoDisposablesAreLeakedInTestSuite(): Pick void): void { +export function throwIfDisposablesAreLeaked(body: () => void, logToConsole = true): void { const tracker = new DisposableTracker(); setDisposableTracker(tracker); body(); setDisposableTracker(null); - tracker.ensureNoLeakingDisposables(); + tracker.ensureNoLeakingDisposables(logToConsole); } export async function throwIfDisposablesAreLeakedAsync(body: () => Promise): Promise { diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index 7eb51c59d6594..f3a2b7789bf67 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -25,6 +25,7 @@ export class ToggleCollapseUnchangedRegions extends Action2 { title: { value: localize('toggleCollapseUnchangedRegions', "Toggle Collapse Unchanged Regions"), original: 'Toggle Collapse Unchanged Regions' }, icon: Codicon.map, toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'), + precondition: ContextKeyExpr.has('isInDiffEditor'), menu: { id: MenuId.EditorTitle, order: 22, @@ -47,6 +48,7 @@ export class ToggleShowMovedCodeBlocks extends Action2 { super({ id: 'diffEditor.toggleShowMovedCodeBlocks', title: { value: localize('toggleShowMovedCodeBlocks', "Toggle Show Moved Code Blocks"), original: 'Toggle Show Moved Code Blocks' }, + precondition: ContextKeyExpr.has('isInDiffEditor'), }); } @@ -64,6 +66,7 @@ export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 { super({ id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited', title: { value: localize('toggleUseInlineViewWhenSpaceIsLimited', "Toggle Use Inline View When Space Is Limited"), original: 'Toggle Use Inline View When Space Is Limited' }, + precondition: ContextKeyExpr.has('isInDiffEditor'), }); } @@ -84,7 +87,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }, order: 11, group: '1_diff', - when: EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached, + when: ContextKeyExpr.and( + EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached, + ContextKeyExpr.has('isInDiffEditor'), + ), }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -96,6 +102,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }, order: 10, group: '1_diff', + when: ContextKeyExpr.has('isInDiffEditor'), }); const diffEditorCategory: ILocalizedString = { diff --git a/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts b/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts index c67fd8822afde..edaf0c6e09925 100644 --- a/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts +++ b/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts @@ -316,24 +316,24 @@ class MovedBlockOverlayWidget extends ViewZoneOverlayWidget { 'codeMovedToWithChanges', 'Code moved with changes to line {0}-{1}', this._move.lineRangeMapping.modified.startLineNumber, - this._move.lineRangeMapping.modified.endLineNumberExclusive + this._move.lineRangeMapping.modified.endLineNumberExclusive - 1, ) : localize( 'codeMovedFromWithChanges', 'Code moved with changes from line {0}-{1}', this._move.lineRangeMapping.original.startLineNumber, - this._move.lineRangeMapping.original.endLineNumberExclusive + this._move.lineRangeMapping.original.endLineNumberExclusive - 1, ); } else { text = this._kind === 'original' ? localize( 'codeMovedTo', 'Code moved to line {0}-{1}', this._move.lineRangeMapping.modified.startLineNumber, - this._move.lineRangeMapping.modified.endLineNumberExclusive + this._move.lineRangeMapping.modified.endLineNumberExclusive - 1, ) : localize( 'codeMovedFrom', 'Code moved from line {0}-{1}', this._move.lineRangeMapping.original.startLineNumber, - this._move.lineRangeMapping.original.endLineNumberExclusive + this._move.lineRangeMapping.original.endLineNumberExclusive - 1, ); } diff --git a/src/vs/editor/browser/widget/diffEditor/style.css b/src/vs/editor/browser/widget/diffEditor/style.css index 6e52932ef8e1f..d81f5cf2ebedb 100644 --- a/src/vs/editor/browser/widget/diffEditor/style.css +++ b/src/vs/editor/browser/widget/diffEditor/style.css @@ -286,3 +286,14 @@ .monaco-diff-editor .diffViewport:active { background: var(--vscode-scrollbarSlider-activeBackground); } + +.monaco-editor .diagonal-fill { + background-image: linear-gradient( + -45deg, + var(--vscode-diffEditor-diagonalFill) 12.5%, + #0000 12.5%, #0000 50%, + var(--vscode-diffEditor-diagonalFill) 50%, var(--vscode-diffEditor-diagonalFill) 62.5%, + #0000 62.5%, #0000 100% + ); + background-size: 8px 8px; +} diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index c6e7a926213e3..077393ebb513f 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -39,7 +39,13 @@ export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuAct fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation'); } -export function createAndFillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean): void { +export function createAndFillInActionBarActions( + menu: IMenu, + options: IMenuActionOptions | undefined, + target: IAction[] | { primary: IAction[]; secondary: IAction[] }, + primaryGroup?: string | ((actionGroup: string) => boolean), + shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, + useSeparatorsInPrimaryActions?: boolean): void { const groups = menu.getActions(options); const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup; @@ -102,7 +108,7 @@ function fillInActions( // inlining submenus with length 0 or 1 is easy, // larger submenus need to be checked with the overall limit const submenuActions = action.actions; - if (submenuActions.length <= 1 && shouldInlineSubmenu(action, group, target.length)) { + if (shouldInlineSubmenu(action, group, target.length)) { target.splice(index, 1, ...submenuActions); } } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0b6f1148464c4..62feb6c249651 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -74,6 +74,7 @@ export class MenuId { static readonly ExtensionContext = new MenuId('ExtensionContext'); static readonly GlobalActivity = new MenuId('GlobalActivity'); static readonly CommandCenter = new MenuId('CommandCenter'); + static readonly CommandCenterCenter = new MenuId('CommandCenterCenter'); static readonly LayoutControlMenuSubmenu = new MenuId('LayoutControlMenuSubmenu'); static readonly LayoutControlMenu = new MenuId('LayoutControlMenu'); static readonly MenubarMainMenu = new MenuId('MenubarMainMenu'); diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 84545808c0f3f..f4f7c8ee4a7a3 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -14,7 +14,7 @@ import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticToken import { validateWhenClauses } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { matchesSomeScheme } from 'vs/platform/opener/common/opener'; -import { ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto, IRawColorInfo, ITypeHierarchyItemDto, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ICallHierarchyItemDto, IIncomingCallDto, IInlineValueContextDto, IOutgoingCallDto, IRawColorInfo, ITypeHierarchyItemDto, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; @@ -385,7 +385,11 @@ const newCommands: ApiCommand[] = [ // --- debug support new ApiCommand( 'vscode.executeInlineValueProvider', '_executeInlineValueProvider', 'Execute inline value provider', - [ApiCommandArgument.Uri, ApiCommandArgument.Range], + [ + ApiCommandArgument.Uri, + ApiCommandArgument.Range, + new ApiCommandArgument('context', 'An InlineValueContext', v => v && typeof v.frameId === 'number' && v.stoppedLocation instanceof types.Range, v => typeConverters.InlineValueContext.from(v)) + ], new ApiCommandResult('A promise that resolves to an array of InlineValue objects', result => { return result.map(typeConverters.InlineValue.to); }) diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 561f731654ddc..2ab734a61a6c0 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -22,7 +22,7 @@ import { NodeRemoteTunnel } from 'vs/platform/tunnel/node/tunnelService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; -import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; +import { CandidatePort, parseAddress } from 'vs/workbench/services/remote/common/tunnelModel'; import * as vscode from 'vscode'; export function getSockets(stdout: string): Record { @@ -349,7 +349,7 @@ export class NodeExtHostTunnelService extends ExtHostTunnelService { const disposeEmitter = new Emitter(); return { - localAddress: t.localAddress, + localAddress: parseAddress(t.localAddress) ?? t.localAddress, remoteAddress: { port: t.tunnelRemotePort, host: t.tunnelRemoteHost }, onDidDispose: disposeEmitter.event, dispose: () => { diff --git a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts index 0a04c9317765a..4764659511340 100644 --- a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts @@ -69,7 +69,7 @@ suite('ExtHostLanguageFeatures', function () { let originalErrorHandler: (e: any) => any; let instantiationService: TestInstantiationService; - suiteSetup(() => { + setup(() => { model = createTextModel( [ @@ -137,15 +137,14 @@ suite('ExtHostLanguageFeatures', function () { mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, disposables.add(inst.createInstance(MainThreadLanguageFeatures, rpcProtocol))); }); - suiteTeardown(() => { + teardown(() => { + disposables.clear(); + setUnexpectedErrorHandler(originalErrorHandler); model.dispose(); mainThread.dispose(); instantiationService.dispose(); - }); - teardown(() => { - disposables.clear(); return rpcProtocol.sync(); }); diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts index b501e78d5ea86..6e8d0459e6c7e 100644 --- a/src/vs/workbench/browser/actions/quickAccessActions.ts +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -14,6 +14,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { inQuickPickContext, defaultQuickAccessContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { AnythingQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess'; +import { Codicon } from 'vs/base/common/codicons'; //#region Quick access management commands and keys @@ -155,8 +156,9 @@ registerAction2(class QuickAccessAction extends Action2 { super({ id: 'workbench.action.quickOpenWithModes', title: localize('quickOpenWithModes', "Quick Open"), + icon: Codicon.search, menu: { - id: MenuId.CommandCenter, + id: MenuId.CommandCenterCenter, order: 100 } }); diff --git a/src/vs/workbench/browser/iframe.ts b/src/vs/workbench/browser/iframe.ts deleted file mode 100644 index cd6bfa2be82db..0000000000000 --- a/src/vs/workbench/browser/iframe.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -/** - * Returns a sha-256 composed of `parentOrigin` and `salt` converted to base 32 - */ -export async function parentOriginHash(parentOrigin: string, salt: string): Promise { - // This same code is also inlined at `src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html` - if (!crypto.subtle) { - throw new Error(`'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).`); - } - - const strData = JSON.stringify({ parentOrigin, salt }); - const encoder = new TextEncoder(); - const arrData = encoder.encode(strData); - const hash = await crypto.subtle.digest('sha-256', arrData); - return sha256AsBase32(hash); -} - -function sha256AsBase32(bytes: ArrayBuffer): string { - const array = Array.from(new Uint8Array(bytes)); - const hexArray = array.map(b => b.toString(16).padStart(2, '0')).join(''); - // sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32 - return BigInt(`0x${hexArray}`).toString(32).padStart(52, '0'); -} diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 79d341b32d0a1..86b9a9721b09d 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -39,6 +39,7 @@ import { IOutline } from 'vs/workbench/services/outline/browser/outline'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Emitter } from 'vs/base/common/event'; class OutlineItem extends BreadcrumbsItem { @@ -501,6 +502,59 @@ export class BreadcrumbsControl { } } +export class BreadcrumbsControlFactory { + + private readonly _disposables = new DisposableStore(); + + private _control: BreadcrumbsControl | undefined; + get control() { return this._control; } + + private readonly _onDidEnablementChange = this._disposables.add(new Emitter()); + get onDidEnablementChange() { return this._onDidEnablementChange.event; } + + constructor( + container: HTMLElement, + editorGroup: IEditorGroupView, + options: IBreadcrumbsControlOptions, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IFileService fileService: IFileService + ) { + const config = this._disposables.add(BreadcrumbsConfig.IsEnabled.bindTo(configurationService)); + this._disposables.add(config.onDidChange(() => { + const value = config.getValue(); + if (!value && this._control) { + this._control.dispose(); + this._control = undefined; + this._onDidEnablementChange.fire(); + } else if (value && !this._control) { + this._control = instantiationService.createInstance(BreadcrumbsControl, container, options, editorGroup); + this._control.update(); + this._onDidEnablementChange.fire(); + } + })); + + if (config.getValue()) { + this._control = instantiationService.createInstance(BreadcrumbsControl, container, options, editorGroup); + } + + this._disposables.add(fileService.onDidChangeFileSystemProviderRegistrations(e => { + if (this._control?.model && this._control.model.resource.scheme !== e.scheme) { + // ignore if the scheme of the breadcrumbs resource is not affected + return; + } + if (this._control?.update()) { + this._onDidEnablementChange.fire(); + } + })); + } + + dispose(): void { + this._disposables.dispose(); + this._control?.dispose(); + } +} + //#region commands // toggle command diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 36f68774d5fa7..42870c2ccdce7 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -66,7 +66,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorHandler } from 'vs/workbench/services/untitled/common/untitledTextEditorHandler'; import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; -import { AccessibilityStatus } from 'vs/workbench/browser/parts/editor/accessibilityStatus'; import { ToggleTabsVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; //#region Editor Registrations @@ -126,7 +125,6 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorAutoSave, LifecyclePhase.Ready); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AccessibilityStatus, LifecyclePhase.Ready); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(UntitledTextEditorWorkingCopyEditorHandler, LifecyclePhase.Ready); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DynamicEditorConfigurations, LifecyclePhase.Ready); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index c8cdf695fcb49..8764654ad5a12 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -118,7 +118,7 @@ export interface IEditorGroupTitleHeight { /** * The height offset to e.g. use when drawing drop overlays. * This number may be smaller than `height` if the title control - * decides to have an `offset` that is within the title area + * decides to have an `offset` that is within the title control * (e.g. when breadcrumbs are enabled). */ offset: number; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 01b92a4b7e2b8..f013b140ff442 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -11,7 +11,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor, IDomNodePagePosition } from 'vs/base/browser/dom'; +import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; @@ -19,7 +19,6 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme'; import { ICloseEditorsFilter, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorPanes } from 'vs/workbench/browser/parts/editor/editorPanes'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; @@ -29,12 +28,10 @@ import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DeferredPromise, Promises, RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; -import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IEditorGroupsAccessor, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions } from 'vs/workbench/browser/parts/editor/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAction } from 'vs/base/common/actions'; -import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -55,6 +52,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { EditorGroupWatermark } from 'vs/workbench/browser/parts/editor/editorGroupWatermark'; +import { EditorTitleControl } from 'vs/workbench/browser/parts/editor/editorTitleControl'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -118,7 +116,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly scopedInstantiationService: IInstantiationService; private readonly titleContainer: HTMLElement; - private titleAreaControl: TitleControl; + private readonly titleControl: EditorTitleControl; private readonly progressBar: ProgressBar; @@ -200,7 +198,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.appendChild(this.titleContainer); // Title control - this.titleAreaControl = this.createTitleAreaControl(); + this.titleControl = this._register(this.scopedInstantiationService.createInstance(EditorTitleControl, this.titleContainer, this.accessor, this)); // Editor container this.editorContainer = document.createElement('div'); @@ -458,24 +456,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleContainer.classList.toggle('show-file-icons', this.accessor.partOptions.showIcons); } - private createTitleAreaControl(): TitleControl { - - // Clear old if existing - if (this.titleAreaControl) { - this.titleAreaControl.dispose(); - clearNode(this.titleContainer); - } - - // Create new based on options - if (this.accessor.partOptions.showTabs) { - this.titleAreaControl = this.scopedInstantiationService.createInstance(TabsTitleControl, this.titleContainer, this.accessor, this); - } else { - this.titleAreaControl = this.scopedInstantiationService.createInstance(NoTabsTitleControl, this.titleContainer, this.accessor, this); - } - - return this.titleAreaControl; - } - private restoreEditors(from: IEditorGroupView | ISerializedEditorGroupModel | null): Promise | undefined { if (this.count === 0) { return; // nothing to show @@ -702,26 +682,21 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Title container this.updateTitleContainer(); + // Title control + this.titleControl.updateOptions(event.oldPartOptions, event.newPartOptions); + // Title control Switch between showing tabs <=> not showing tabs if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) { - // Recreate title control - this.createTitleAreaControl(); - // Re-layout this.relayout(); // Ensure to show active editor if any if (this.model.activeEditor) { - this.titleAreaControl.openEditor(this.model.activeEditor); + this.titleControl.openEditor(this.model.activeEditor); } } - // Just update title control - else { - this.titleAreaControl.updateOptions(event.oldPartOptions, event.newPartOptions); - } - // Styles this.updateStyles(); @@ -739,13 +714,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.pinEditor(editor); // Forward to title control - this.titleAreaControl.updateEditorDirty(editor); + this.titleControl.updateEditorDirty(editor); } private onDidChangeEditorLabel(editor: EditorInput): void { // Forward to title control - this.titleAreaControl.updateEditorLabel(editor); + this.titleControl.updateEditorLabel(editor); } private onDidVisibilityChange(visible: boolean): void { @@ -780,7 +755,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } get titleHeight(): IEditorGroupTitleHeight { - return this.titleAreaControl.getHeight(); + return this.titleControl.getHeight(); } notifyIndexChanged(newIndex: number): void { @@ -798,7 +773,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.classList.toggle('inactive', !isActive); // Update title control - this.titleAreaControl.setActive(isActive); + this.titleControl.setActive(isActive); // Update styles this.updateStyles(); @@ -925,7 +900,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control if (editor) { - this.titleAreaControl.pinEditor(editor); + this.titleControl.pinEditor(editor); } } } @@ -952,14 +927,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // title control and also make sure to emit this as an event const newIndexOfEditor = this.getIndexOfEditor(editor); if (newIndexOfEditor !== oldIndexOfEditor) { - this.titleAreaControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor); + this.titleControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor); } // Forward sticky state to title control if (sticky) { - this.titleAreaControl.stickEditor(editor); + this.titleControl.stickEditor(editor); } else { - this.titleAreaControl.unstickEditor(editor); + this.titleControl.unstickEditor(editor); } } } @@ -1119,7 +1094,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Show in title control after editor control because some actions depend on it // but respect the internal options in case title control updates should skip. if (!internalOptions?.skipTitleUpdate) { - this.titleAreaControl.openEditor(editor); + this.titleControl.openEditor(editor); } return openEditorPromise; @@ -1169,7 +1144,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { })); // Update the title control all at once with all editors - this.titleAreaControl.openEditors(inactiveEditors.map(({ editor }) => editor)); + this.titleControl.openEditors(inactiveEditors.map(({ editor }) => editor)); // Opening many editors at once can put any editor to be // the active one depending on options. As such, we simply @@ -1200,8 +1175,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // in source and target if the title update was skipped if (internalOptions.skipTitleUpdate) { const movedEditors = editors.map(({ editor }) => editor); - target.titleAreaControl.openEditors(movedEditors); - this.titleAreaControl.closeEditors(movedEditors); + target.titleControl.openEditors(movedEditors); + this.titleControl.closeEditors(movedEditors); } } @@ -1241,9 +1216,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.model.moveEditor(editor, moveToIndex); this.model.pin(editor); - // Forward to title area - this.titleAreaControl.moveEditor(editor, currentIndex, moveToIndex); - this.titleAreaControl.pinEditor(editor); + // Forward to title control + this.titleControl.moveEditor(editor, currentIndex, moveToIndex); + this.titleControl.pinEditor(editor); } private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: EditorGroupView, openOptions?: IEditorOpenOptions, internalOptions?: IInternalMoveCopyOptions): void { @@ -1299,7 +1274,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // in target if the title update was skipped if (internalOptions.skipTitleUpdate) { const copiedEditors = editors.map(({ editor }) => editor); - target.titleAreaControl.openEditors(copiedEditors); + target.titleControl.openEditors(copiedEditors); } } @@ -1346,7 +1321,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control unless skipped via internal options if (!internalOptions?.skipTitleUpdate) { - this.titleAreaControl.beforeCloseEditor(editor); + this.titleControl.beforeCloseEditor(editor); } // Closing the active editor of the group is a bit more work @@ -1361,7 +1336,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control unless skipped via internal options if (!internalOptions?.skipTitleUpdate) { - this.titleAreaControl.closeEditor(editor); + this.titleControl.closeEditor(editor); } } @@ -1711,7 +1686,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control if (editors.length) { - this.titleAreaControl.closeEditors(editors); + this.titleControl.closeEditors(editors); } } @@ -1763,7 +1738,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control if (editorsToClose.length) { - this.titleAreaControl.closeEditors(editorsToClose); + this.titleControl.closeEditors(editorsToClose); } } @@ -1922,16 +1897,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.lastLayout = { width, height, top, left }; this.element.classList.toggle('max-height-478px', height <= 478); - // Layout the title area first to receive the size it occupies - const titleAreaSize = this.titleAreaControl.layout({ + // Layout the title control first to receive the size it occupies + const titleControlSize = this.titleControl.layout({ container: new Dimension(width, height), available: new Dimension(width, height - this.editorPane.minimumHeight) }); // Pass the container width and remaining height to the editor layout - const editorHeight = Math.max(0, height - titleAreaSize.height); + const editorHeight = Math.max(0, height - titleControlSize.height); this.editorContainer.style.height = `${editorHeight}px`; - this.editorPane.layout({ width, height: editorHeight, top: top + titleAreaSize.height, left }); + this.editorPane.layout({ width, height: editorHeight, top: top + titleControlSize.height, left }); } relayout(): void { @@ -1956,8 +1931,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onWillDispose.fire(); - this.titleAreaControl.dispose(); - super.dispose(); } } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index a73ebcd20538c..5b9869395f957 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -42,7 +42,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { getIconClassesForLanguageId } from 'vs/editor/common/services/getIconClasses'; import { Promises, timeout } from 'vs/base/common/async'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IMarker, IMarkerService, MarkerSeverity, IMarkerData } from 'vs/platform/markers/common/markers'; @@ -54,7 +54,7 @@ import { Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { TabFocusMode } from 'vs/workbench/browser/parts/editor/tabFocus'; +import { TabFocus } from 'vs/editor/browser/config/tabFocus'; class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private primary: IEncodingSupport, private secondary: IEncodingSupport) { } @@ -278,6 +278,36 @@ class State { } } +class TabFocusMode extends Disposable { + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + constructor(@IConfigurationService private readonly configurationService: IConfigurationService) { + super(); + + this.registerListeners(); + + const tabFocusModeConfig = configurationService.getValue('editor.tabFocusMode') === true ? true : false; + TabFocus.setTabFocusMode(tabFocusModeConfig); + + this._onDidChange.fire(tabFocusModeConfig); + } + + private registerListeners(): void { + this._register(TabFocus.onDidChangeTabFocus(tabFocusMode => this._onDidChange.fire(tabFocusMode))); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.tabFocusMode')) { + const tabFocusModeConfig = this.configurationService.getValue('editor.tabFocusMode') === true ? true : false; + TabFocus.setTabFocusMode(tabFocusModeConfig); + + this._onDidChange.fire(tabFocusModeConfig); + } + })); + } +} + const nlsSingleSelectionRange = localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)"); const nlsSingleSelection = localize('singleSelection', "Ln {0}, Col {1}"); const nlsMultiSelectionRange = localize('multiSelectionRange', "{0} selections ({1} characters selected)"); diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts similarity index 83% rename from src/vs/workbench/browser/parts/editor/titleControl.ts rename to src/vs/workbench/browser/parts/editor/editorTabsControl.ts index d09cbb7008bc5..67e142dea056c 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/titlecontrol'; +import 'vs/css!./media/editortabscontrol'; import { localize } from 'vs/nls'; import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; @@ -11,10 +11,9 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, SubmenuAction, ActionRunner } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -25,14 +24,11 @@ import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; -import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; -import { IEditorGroupsAccessor, IEditorGroupTitleHeight, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { IFileService } from 'vs/platform/files/common/files'; import { assertIsDefined } from 'vs/base/common/types'; import { isFirefox } from 'vs/base/browser/browser'; import { isCancellationError } from 'vs/base/common/errors'; @@ -41,24 +37,11 @@ import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; export interface IToolbarActions { - primary: IAction[]; - secondary: IAction[]; -} - -export interface ITitleControlDimensions { - - /** - * The size of the parent container the title control is layed out in. - */ - container: Dimension; - - /** - * The maximum size the title control is allowed to consume based on - * other controls that are positioned inside the container. - */ - available: Dimension; + readonly primary: IAction[]; + readonly secondary: IAction[]; } export class EditorCommandsContextActionRunner extends ActionRunner { @@ -87,19 +70,17 @@ export class EditorCommandsContextActionRunner extends ActionRunner { } } -export abstract class TitleControl extends Themable { +export abstract class EditorTabsControl extends Themable { protected readonly editorTransfer = LocalSelectionTransfer.getInstance(); protected readonly groupTransfer = LocalSelectionTransfer.getInstance(); protected readonly treeItemsTransfer = LocalSelectionTransfer.getInstance(); - private static readonly EDITOR_TITLE_HEIGHT = { - normal: 35, - compact: 22 + private static readonly EDITOR_TAB_HEIGHT = { + normal: 35 as const, + compact: 22 as const }; - protected breadcrumbsControl: BreadcrumbsControl | undefined = undefined; - private editorActionsToolbar: WorkbenchToolBar | undefined; private resourceContext: ResourceContextKey; @@ -131,8 +112,6 @@ export abstract class TitleControl extends Themable { @IMenuService private readonly menuService: IMenuService, @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IConfigurationService protected configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService ) { super(themeService); @@ -156,41 +135,9 @@ export abstract class TitleControl extends Themable { } protected create(parent: HTMLElement): void { - this.updateTitleHeight(); + this.updateTabHeight(); } - protected createBreadcrumbsControl(container: HTMLElement, options: IBreadcrumbsControlOptions): void { - const config = this._register(BreadcrumbsConfig.IsEnabled.bindTo(this.configurationService)); - this._register(config.onDidChange(() => { - const value = config.getValue(); - if (!value && this.breadcrumbsControl) { - this.breadcrumbsControl.dispose(); - this.breadcrumbsControl = undefined; - this.handleBreadcrumbsEnablementChange(); - } else if (value && !this.breadcrumbsControl) { - this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group); - this.breadcrumbsControl.update(); - this.handleBreadcrumbsEnablementChange(); - } - })); - - if (config.getValue()) { - this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group); - } - - this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { - if (this.breadcrumbsControl?.model && this.breadcrumbsControl.model.resource.scheme !== e.scheme) { - // ignore if the scheme of the breadcrumbs resource is not affected - return; - } - if (this.breadcrumbsControl?.update()) { - this.handleBreadcrumbsEnablementChange(); - } - })); - } - - protected abstract handleBreadcrumbsEnablementChange(): void; - protected createEditorActionsToolBar(container: HTMLElement): void { const context: IEditorCommandsContext = { groupId: this.group.id }; @@ -429,25 +376,25 @@ export abstract class TitleControl extends Themable { return keybinding ? keybinding.getLabel() ?? undefined : undefined; } - protected get titleHeight() { - return this.accessor.partOptions.tabHeight !== 'compact' ? TitleControl.EDITOR_TITLE_HEIGHT.normal : TitleControl.EDITOR_TITLE_HEIGHT.compact; + protected get tabHeight() { + return this.accessor.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact; } - protected updateTitleHeight(): void { - this.parent.style.setProperty('--editor-group-title-height', `${this.titleHeight}px`); + protected updateTabHeight(): void { + this.parent.style.setProperty('--editor-group-tab-height', `${this.tabHeight}px`); } updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void { - // Update title height + // Update tab height if (oldOptions.tabHeight !== newOptions.tabHeight) { - this.updateTitleHeight(); + this.updateTabHeight(); } } - abstract openEditor(editor: EditorInput): void; + abstract openEditor(editor: EditorInput): boolean; - abstract openEditors(editors: EditorInput[]): void; + abstract openEditors(editors: EditorInput[]): boolean; abstract beforeCloseEditor(editor: EditorInput): void; @@ -469,14 +416,7 @@ export abstract class TitleControl extends Themable { abstract updateEditorDirty(editor: EditorInput): void; - abstract layout(dimensions: ITitleControlDimensions): Dimension; - - abstract getHeight(): IEditorGroupTitleHeight; + abstract layout(dimensions: IEditorTitleControlDimensions): Dimension; - override dispose(): void { - dispose(this.breadcrumbsControl); - this.breadcrumbsControl = undefined; - - super.dispose(); - } + abstract getHeight(): number; } diff --git a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts new file mode 100644 index 0000000000000..ecdc4f67a5714 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/editortitlecontrol'; +import { Dimension, clearNode } from 'vs/base/browser/dom'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { BreadcrumbsControl, BreadcrumbsControlFactory } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; +import { IEditorGroupsAccessor, IEditorGroupTitleHeight, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; +import { MultiEditorTabsControl } from 'vs/workbench/browser/parts/editor/multiEditorTabsControl'; +import { SingleEditorTabsControl } from 'vs/workbench/browser/parts/editor/singleEditorTabsControl'; +import { IEditorPartOptions } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +export interface IEditorTitleControlDimensions { + + /** + * The size of the parent container the title control is layed out in. + */ + readonly container: Dimension; + + /** + * The maximum size the title control is allowed to consume based on + * other controls that are positioned inside the container. + */ + readonly available: Dimension; +} + +export class EditorTitleControl extends Themable { + + private editorTabsControl: EditorTabsControl; + private editorTabsControlDisposable = this._register(new DisposableStore()); + + private breadcrumbsControlFactory: BreadcrumbsControlFactory | undefined; + private breadcrumbsControlDisposables = this._register(new DisposableStore()); + private get breadcrumbsControl() { return this.breadcrumbsControlFactory?.control; } + + constructor( + private parent: HTMLElement, + private accessor: IEditorGroupsAccessor, + private group: IEditorGroupView, + @IInstantiationService private instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService + ) { + super(themeService); + + this.editorTabsControl = this.createEditorTabsControl(); + this.breadcrumbsControlFactory = this.createBreadcrumbsControl(); + } + + private createEditorTabsControl(): EditorTabsControl { + let control: EditorTabsControl; + if (this.accessor.partOptions.showTabs) { + control = this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.group); + } else { + control = this.instantiationService.createInstance(SingleEditorTabsControl, this.parent, this.accessor, this.group); + } + + return this.editorTabsControlDisposable.add(control); + } + + private createBreadcrumbsControl(): BreadcrumbsControlFactory | undefined { + if (!this.accessor.partOptions.showTabs) { + return undefined; // single tabs have breadcrumbs inlined + } + + // Breadcrumbs container + const breadcrumbsContainer = document.createElement('div'); + breadcrumbsContainer.classList.add('breadcrumbs-below-tabs'); + this.parent.appendChild(breadcrumbsContainer); + + const breadcrumbsControlFactory = this.breadcrumbsControlDisposables.add(this.instantiationService.createInstance(BreadcrumbsControlFactory, breadcrumbsContainer, this.group, { + showFileIcons: true, + showSymbolIcons: true, + showDecorationColors: false, + showPlaceholder: true + })); + this.breadcrumbsControlDisposables.add(breadcrumbsControlFactory.onDidEnablementChange(() => this.handleBreadcrumbsEnablementChange())); + + return breadcrumbsControlFactory; + } + + private handleBreadcrumbsEnablementChange(): void { + this.group.relayout(); // relayout when breadcrumbs are enable/disabled + } + + openEditor(editor: EditorInput): void { + const didChange = this.editorTabsControl.openEditor(editor); + + this.handleOpenedEditors(didChange); + } + + openEditors(editors: EditorInput[]): void { + const didChange = this.editorTabsControl.openEditors(editors); + + this.handleOpenedEditors(didChange); + } + + private handleOpenedEditors(didChange: boolean): void { + if (didChange) { + this.breadcrumbsControl?.update(); + } else { + this.breadcrumbsControl?.revealLast(); + } + } + + beforeCloseEditor(editor: EditorInput): void { + return this.editorTabsControl.beforeCloseEditor(editor); + } + + closeEditor(editor: EditorInput): void { + this.editorTabsControl.closeEditor(editor); + + this.handleClosedEditors(); + } + + closeEditors(editors: EditorInput[]): void { + this.editorTabsControl.closeEditors(editors); + + this.handleClosedEditors(); + } + + private handleClosedEditors(): void { + if (!this.group.activeEditor) { + this.breadcrumbsControl?.update(); + } + } + + moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number): void { + return this.editorTabsControl.moveEditor(editor, fromIndex, targetIndex); + } + + pinEditor(editor: EditorInput): void { + return this.editorTabsControl.pinEditor(editor); + } + + stickEditor(editor: EditorInput): void { + return this.editorTabsControl.stickEditor(editor); + } + + unstickEditor(editor: EditorInput): void { + return this.editorTabsControl.unstickEditor(editor); + } + + setActive(isActive: boolean): void { + return this.editorTabsControl.setActive(isActive); + } + + updateEditorLabel(editor: EditorInput): void { + return this.editorTabsControl.updateEditorLabel(editor); + } + + updateEditorDirty(editor: EditorInput): void { + return this.editorTabsControl.updateEditorDirty(editor); + } + + updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void { + + // Update editor tabs control if options changed + if (oldOptions.showTabs !== newOptions.showTabs) { + + // Clear old + this.editorTabsControlDisposable.clear(); + this.breadcrumbsControlDisposables.clear(); + clearNode(this.parent); + + // Create new + this.editorTabsControl = this.createEditorTabsControl(); + this.breadcrumbsControlFactory = this.createBreadcrumbsControl(); + } + + // Forward into editor tabs control + this.editorTabsControl.updateOptions(oldOptions, newOptions); + } + + layout(dimensions: IEditorTitleControlDimensions): Dimension { + + // Layout tabs control + const tabsControlDimension = this.editorTabsControl.layout(dimensions); + + // Layout breadcrumbs if visible + let breadcrumbsControlDimension: Dimension | undefined = undefined; + if (this.breadcrumbsControl?.isHidden() === false) { + breadcrumbsControlDimension = new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT); + this.breadcrumbsControl.layout(breadcrumbsControlDimension); + } + + return new Dimension( + dimensions.container.width, + tabsControlDimension.height + (breadcrumbsControlDimension ? breadcrumbsControlDimension.height : 0) + ); + } + + getHeight(): IEditorGroupTitleHeight { + const tabsControlHeight = this.editorTabsControl.getHeight(); + const breadcrumbsControlHeight = this.breadcrumbsControl?.isHidden() === false ? BreadcrumbsControl.HEIGHT : 0; + + return { + total: tabsControlHeight + breadcrumbsControlHeight, + offset: tabsControlHeight + }; + } +} diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css similarity index 95% rename from src/vs/workbench/browser/parts/editor/media/titlecontrol.css rename to src/vs/workbench/browser/parts/editor/media/editortabscontrol.css index 0b22793aacd05..bf44c02aee235 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css @@ -30,7 +30,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before { - height: var(--editor-group-title-height); /* tweak the icon size of the editor labels when icons are enabled */ + height: var(--editor-group-tab-height); /* tweak the icon size of the editor labels when icons are enabled */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .monaco-icon-label::after, diff --git a/src/vs/workbench/browser/parts/editor/media/editortitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/editortitlecontrol.css new file mode 100644 index 0000000000000..a24f76131a86b --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/editortitlecontrol.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +/* Breadcrumbs (below multiple editor tabs) */ + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control { + flex: 1 100%; + height: 22px; + cursor: default; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-icon-label { + height: 22px; + line-height: 22px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-icon-label::before { + height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .outline-element-icon { + padding-right: 3px; + height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ + line-height: 22px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item { + max-width: 80%; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item::before { + width: 16px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item:last-child { + padding-right: 8px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { + display: none; /* hides chevrons when last item */ +} diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css similarity index 92% rename from src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css rename to src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index f5b39db85fcf0..d51863edf23eb 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -71,7 +71,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container { display: flex; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); scrollbar-width: none; /* Firefox: hide scrollbar */ } @@ -97,7 +97,7 @@ display: flex; white-space: nowrap; cursor: pointer; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); box-sizing: border-box; padding-left: 10px; } @@ -265,7 +265,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label { margin-top: auto; margin-bottom: auto; - line-height: var(--editor-group-title-height); /* aligns icon and label vertically centered in the tab */ + line-height: var(--editor-group-tab-height); /* aligns icon and label vertically centered in the tab */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink .tab-label, @@ -424,56 +424,13 @@ pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */ } -/* Breadcrumbs */ - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control { - flex: 1 100%; - height: 22px; - cursor: default; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label { - height: 22px; - line-height: 22px; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label::before { - height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .outline-element-icon { - padding-right: 3px; - height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ - line-height: 22px; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item { - max-width: 80%; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { - width: 16px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child { - padding-right: 8px; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { - display: none; /* hides chevrons when last item */ -} - /* Editor Actions Toolbar */ .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { cursor: default; flex: initial; padding: 0 8px 0 4px; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); } .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-item { diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css similarity index 96% rename from src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css rename to src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css index 81562a812815f..893920c71c580 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css @@ -6,7 +6,7 @@ /* Title Label */ .monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container { - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); display: flex; justify-content: flex-start; align-items: center; @@ -15,7 +15,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label { - line-height: var(--editor-group-title-height); + line-height: var(--editor-group-tab-height); overflow: hidden; text-overflow: ellipsis; position: relative; @@ -26,7 +26,7 @@ flex: initial; /* helps to show decorations right next to the label and not at the end while still preserving text overflow ellipsis */ } -/* Breadcrumbs */ +/* Breadcrumbs (inline next to single editor tab) */ .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label { flex: none; @@ -94,7 +94,7 @@ flex: initial; opacity: 0.5; padding-right: 8px; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); } .monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions .action-item { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts similarity index 94% rename from src/vs/workbench/browser/parts/editor/tabsTitleControl.ts rename to src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index ac3fced8d68fa..e3e4de2bc0e17 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/tabstitlecontrol'; +import 'vs/css!./media/multieditortabscontrol'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod } from 'vs/workbench/common/editor'; @@ -19,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { EditorCommandsContextActionRunner, ITitleControlDimensions, IToolbarActions, TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; +import { EditorCommandsContextActionRunner, IToolbarActions, EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -34,11 +34,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; -import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; -import { IFileService } from 'vs/platform/files/common/files'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { basenameOrAuthority } from 'vs/base/common/resources'; @@ -55,46 +52,47 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; interface IEditorInputLabel { - editor: EditorInput; + readonly editor: EditorInput; - name?: string; + readonly name?: string; description?: string; - forceDescription?: boolean; - title?: string; - ariaLabel?: string; + readonly forceDescription?: boolean; + readonly title?: string; + readonly ariaLabel?: string; } -interface ITabsTitleControlLayoutOptions { +interface IMultiEditorTabsControlLayoutOptions { /** * Whether to force revealing the active tab, even when * the dimensions have not changed. This can be the case * when a tab was made active and needs to be revealed. */ - forceRevealActiveTab?: true; + readonly forceRevealActiveTab?: true; } -interface IScheduledTabsTitleControlLayout extends IDisposable { +interface IScheduledMultiEditorTabsControlLayout extends IDisposable { /** * Associated options with the layout call. */ - options?: ITabsTitleControlLayoutOptions; + options?: IMultiEditorTabsControlLayoutOptions; } -export class TabsTitleControl extends TitleControl { +export class MultiEditorTabsControl extends EditorTabsControl { private static readonly SCROLLBAR_SIZES = { - default: 3, - large: 10 + default: 3 as const, + large: 10 as const }; private static readonly TAB_WIDTH = { - compact: 38, - shrink: 80, - fit: 120 + compact: 38 as const, + shrink: 80 as const, + fit: 120 as const }; private static readonly DRAG_OVER_OPEN_TAB_THRESHOLD = 1500; @@ -119,12 +117,12 @@ export class TabsTitleControl extends TitleControl { private tabActionBars: ActionBar[] = []; private tabDisposables: IDisposable[] = []; - private dimensions: ITitleControlDimensions & { used?: Dimension } = { + private dimensions: IEditorTitleControlDimensions & { used?: Dimension } = { container: Dimension.None, available: Dimension.None }; - private readonly layoutScheduler = this._register(new MutableDisposable()); + private readonly layoutScheduler = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; private path: IPath = isWindows ? win32 : posix; @@ -144,15 +142,13 @@ export class TabsTitleControl extends TitleControl { @IMenuService menuService: IMenuService, @IQuickInputService quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, - @IFileService fileService: IFileService, @IEditorService private readonly editorService: EditorServiceImpl, @IPathService private readonly pathService: IPathService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, configurationService, fileService, editorResolverService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the @@ -197,12 +193,6 @@ export class TabsTitleControl extends TitleControl { // Editor Actions Toolbar this.createEditorActionsToolBar(this.editorToolbarContainer); - - // Breadcrumbs - const breadcrumbsContainer = document.createElement('div'); - breadcrumbsContainer.classList.add('tabs-breadcrumbs'); - this.titleContainer.appendChild(breadcrumbsContainer); - this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, showPlaceholder: true }); } private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement { @@ -271,10 +261,10 @@ export class TabsTitleControl extends TitleControl { private getTabsScrollbarSizing(): number { if (this.accessor.partOptions.titleScrollbarSizing !== 'large') { - return TabsTitleControl.SCROLLBAR_SIZES.default; + return MultiEditorTabsControl.SCROLLBAR_SIZES.default; } - return TabsTitleControl.SCROLLBAR_SIZES.large; + return MultiEditorTabsControl.SCROLLBAR_SIZES.large; } private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void { @@ -421,7 +411,7 @@ export class TabsTitleControl extends TitleControl { // The restriction is relaxed according to the absolute value of `deltaX` and `deltaY` // to support discrete (mouse wheel) and contiguous scrolling (touchpad) equally well const now = Date.now(); - if (now - this.lastMouseWheelEventTime < TabsTitleControl.MOUSE_WHEEL_EVENT_THRESHOLD - 2 * (Math.abs(e.deltaX) + Math.abs(e.deltaY))) { + if (now - this.lastMouseWheelEventTime < MultiEditorTabsControl.MOUSE_WHEEL_EVENT_THRESHOLD - 2 * (Math.abs(e.deltaX) + Math.abs(e.deltaY))) { return; } @@ -429,9 +419,9 @@ export class TabsTitleControl extends TitleControl { // Figure out scrolling direction but ignore it if too subtle let tabSwitchDirection: number; - if (e.deltaX + e.deltaY < - TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { + if (e.deltaX + e.deltaY < - MultiEditorTabsControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { tabSwitchDirection = -1; - } else if (e.deltaX + e.deltaY > TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { + } else if (e.deltaX + e.deltaY > MultiEditorTabsControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { tabSwitchDirection = 1; } else { return; @@ -490,15 +480,15 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimensions); } - openEditor(editor: EditorInput): void { - this.handleOpenedEditors(); + openEditor(editor: EditorInput): boolean { + return this.handleOpenedEditors(); } - openEditors(editors: EditorInput[]): void { - this.handleOpenedEditors(); + openEditors(editors: EditorInput[]): boolean { + return this.handleOpenedEditors(); } - private handleOpenedEditors(): void { + private handleOpenedEditors(): boolean { // Create tabs as needed const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); @@ -508,7 +498,7 @@ export class TabsTitleControl extends TitleControl { // Make sure to recompute tab labels and detect // if a label change occurred that requires a - // redraw of tabs and update of breadcrumbs. + // redraw of tabs. const activeEditorChanged = this.didActiveEditorChange(); const oldActiveTabLabel = this.activeTabLabel; @@ -516,20 +506,22 @@ export class TabsTitleControl extends TitleControl { this.computeTabLabels(); // Redraw and update in these cases + let didChange = false; if ( activeEditorChanged || // active editor changed oldTabLabelsLength !== this.tabLabels.length || // number of tabs changed !this.equalsEditorInputLabel(oldActiveTabLabel, this.activeTabLabel) // active editor label changed ) { this.redraw({ forceRevealActiveTab: true }); - this.breadcrumbsControl?.update(); + didChange = true; } - // Otherwise only layout for revealing in tabs and breadcrumbs + // Otherwise only layout for revealing else { this.layout(this.dimensions, { forceRevealActiveTab: true }); - this.breadcrumbsControl?.revealLast(); } + + return didChange; } private didActiveEditorChange(): boolean { @@ -617,8 +609,6 @@ export class TabsTitleControl extends TitleControl { this.tabActionBars = []; this.clearEditorActionsToolbar(); - - this.breadcrumbsControl?.update(); } } @@ -1052,7 +1042,7 @@ export class TabsTitleControl extends TitleControl { }, onDragOver: (_, dragDuration) => { - if (dragDuration >= TabsTitleControl.DRAG_OVER_OPEN_TAB_THRESHOLD) { + if (dragDuration >= MultiEditorTabsControl.DRAG_OVER_OPEN_TAB_THRESHOLD) { const draggedOverTab = this.group.getEditorByIndex(index); if (draggedOverTab && this.group.activeEditor !== draggedOverTab) { this.group.openEditor(draggedOverTab, { preserveFocus: true }); @@ -1089,7 +1079,7 @@ export class TabsTitleControl extends TitleControl { if (Array.isArray(data)) { const group = data[0]; if (group.identifier === this.group.id) { - return false; // groups cannot be dropped on title area it originates from + return false; // groups cannot be dropped on group it originates from } } @@ -1251,7 +1241,7 @@ export class TabsTitleControl extends TitleControl { } } - private redraw(options?: ITabsTitleControlLayoutOptions): void { + private redraw(options?: IMultiEditorTabsControlLayoutOptions): void { // Border below tabs if any with explicit high contrast support if (this.tabsAndActionsContainer) { @@ -1322,10 +1312,10 @@ export class TabsTitleControl extends TitleControl { let stickyTabWidth = 0; switch (options.pinnedTabSizing) { case 'compact': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.compact; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.compact; break; case 'shrink': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.shrink; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.shrink; break; } @@ -1525,15 +1515,11 @@ export class TabsTitleControl extends TitleControl { } } - getHeight(): IEditorGroupTitleHeight { - const showsBreadcrumbs = this.breadcrumbsControl && !this.breadcrumbsControl.isHidden(); + getHeight(): number { // Return quickly if our used dimensions are known if (this.dimensions.used) { - return { - total: this.dimensions.used.height, - offset: showsBreadcrumbs ? this.dimensions.used.height - BreadcrumbsControl.HEIGHT : this.dimensions.used.height - }; + return this.dimensions.used.height; } // Otherwise compute via browser APIs @@ -1542,28 +1528,21 @@ export class TabsTitleControl extends TitleControl { } } - private computeHeight(): IEditorGroupTitleHeight { - let total: number; + private computeHeight(): number { + let height: number; // Wrap: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) { - total = this.tabsAndActionsContainer.offsetHeight; + height = this.tabsAndActionsContainer.offsetHeight; } else { - total = this.titleHeight; - } - - const offset = total; - - // Account for breadcrumbs if visible - if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - total += BreadcrumbsControl.HEIGHT; // Account for breadcrumbs if visible + height = this.tabHeight; } - return { total, offset }; + return height; } - layout(dimensions: ITitleControlDimensions, options?: ITabsTitleControlLayoutOptions): Dimension { + layout(dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): Dimension { // Remember dimensions that we get Object.assign(this.dimensions, dimensions); @@ -1591,21 +1570,18 @@ export class TabsTitleControl extends TitleControl { // First time layout: compute the dimensions and store it if (!this.dimensions.used) { - this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight().total); + this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight()); } return this.dimensions.used; } - private doLayout(dimensions: ITitleControlDimensions, options?: ITabsTitleControlLayoutOptions): void { + private doLayout(dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): void { // Only layout if we have valid tab index and dimensions const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) { - // Breadcrumbs - this.doLayoutBreadcrumbs(dimensions); - // Tabs const [activeTab, activeIndex] = activeTabAndIndex; this.doLayoutTabs(activeTab, activeIndex, dimensions, options); @@ -1615,7 +1591,7 @@ export class TabsTitleControl extends TitleControl { // return it fast from the `layout` call without having to // compute it over and over again const oldDimension = this.dimensions.used; - const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight().total); + const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight()); // In case the height of the title control changed from before // (currently only possible if wrapping changed on/off), we need @@ -1626,17 +1602,7 @@ export class TabsTitleControl extends TitleControl { } } - protected handleBreadcrumbsEnablementChange(): void { - this.group.relayout(); // relayout when breadcrumbs are enable/disabled - } - - private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void { - if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - this.breadcrumbsControl.layout(new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT)); - } - } - - private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions, options?: ITabsTitleControlLayoutOptions): void { + private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): void { // Always first layout tabs with wrapping support even if wrapping // is disabled. The result indicates if tabs wrap and if not, we @@ -1649,7 +1615,7 @@ export class TabsTitleControl extends TitleControl { } } - private doLayoutTabsWrapping(dimensions: ITitleControlDimensions): boolean { + private doLayoutTabsWrapping(dimensions: IEditorTitleControlDimensions): boolean { const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorToolbarContainer, this.tabsScrollbar); // Handle wrapping tabs according to setting: @@ -1707,9 +1673,9 @@ export class TabsTitleControl extends TitleControl { // Tabs wrap multiline: remove wrapping under certain size constraint conditions if (tabsWrapMultiLine) { if ( - (tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height - (allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === this.titleHeight) || // if wrapping is not needed anymore - (!lastTabFitsWrapped()) // if last tab does not fit anymore + (tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height + (allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === this.tabHeight) || // if wrapping is not needed anymore + (!lastTabFitsWrapped()) // if last tab does not fit anymore ) { updateTabsWrapping(false); } @@ -1778,7 +1744,7 @@ export class TabsTitleControl extends TitleControl { return tabsWrapMultiLine; } - private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number, options?: ITabsTitleControlLayoutOptions): void { + private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number, options?: IMultiEditorTabsControlLayoutOptions): void { const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); // @@ -1809,10 +1775,10 @@ export class TabsTitleControl extends TitleControl { let stickyTabWidth = 0; switch (this.accessor.partOptions.pinnedTabSizing) { case 'compact': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.compact; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.compact; break; case 'shrink': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.shrink; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.shrink; break; } @@ -1827,7 +1793,7 @@ export class TabsTitleControl extends TitleControl { // is little enough that we need to disable sticky tabs sticky positioning // so that tabs can be scrolled at naturally. let availableTabsContainerWidth = visibleTabsWidth - stickyTabsWidth; - if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_WIDTH.fit) { + if (this.group.stickyCount > 0 && availableTabsContainerWidth < MultiEditorTabsControl.TAB_WIDTH.fit) { tabsContainer.classList.add('disable-sticky-tabs'); availableTabsContainerWidth = visibleTabsWidth; @@ -2153,7 +2119,7 @@ registerThemingParticipant((theme, collector) => { // Hover Border // // Unfortunately we need to copy a lot of CSS over from the - // tabsTitleControl.css because we want to reuse the same + // multiEditorTabsControl.css because we want to reuse the same // styles we already have for the normal bottom-border. const tabHoverBorder = theme.getColor(TAB_HOVER_BORDER); if (tabHoverBorder) { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts similarity index 88% rename from src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts rename to src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 460698f67496c..9912e00a295f2 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/notabstitlecontrol'; +import 'vs/css!./media/singleeditortabscontrol'; import { EditorResourceAccessor, Verbosity, IEditorPartOptions, SideBySideEditor, preventEditorClose, EditorCloseMethod } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { TitleControl, IToolbarActions, ITitleControlDimensions } from 'vs/workbench/browser/parts/editor/titleControl'; +import { EditorTabsControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; @@ -14,29 +14,33 @@ import { addDisposableListener, EventType, EventHelper, Dimension, isAncestor } import { CLOSE_EDITOR_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; -import { IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; import { equals } from 'vs/base/common/objects'; import { toDisposable } from 'vs/base/common/lifecycle'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; +import { BreadcrumbsControlFactory } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; interface IRenderedEditorLabel { - editor?: EditorInput; - pinned: boolean; + readonly editor?: EditorInput; + readonly pinned: boolean; } -export class NoTabsTitleControl extends TitleControl { +export class SingleEditorTabsControl extends EditorTabsControl { private titleContainer: HTMLElement | undefined; private editorLabel: IResourceLabel | undefined; private activeLabel: IRenderedEditorLabel = Object.create(null); + private breadcrumbsControlFactory: BreadcrumbsControlFactory | undefined; + private get breadcrumbsControl() { return this.breadcrumbsControlFactory?.control; } + protected override create(parent: HTMLElement): void { super.create(parent); const titleContainer = this.titleContainer = parent; titleContainer.draggable = true; - //Container listeners + // Container listeners this.registerContainerListeners(titleContainer); // Gesture Support @@ -51,7 +55,14 @@ export class NoTabsTitleControl extends TitleControl { this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e))); // Breadcrumbs - this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, widgetStyles: { ...defaultBreadcrumbsWidgetStyles, breadcrumbsBackground: Color.transparent.toString() }, showPlaceholder: false }); + this.breadcrumbsControlFactory = this._register(this.instantiationService.createInstance(BreadcrumbsControlFactory, labelContainer, this.group, { + showFileIcons: false, + showSymbolIcons: true, + showDecorationColors: false, + widgetStyles: { ...defaultBreadcrumbsWidgetStyles, breadcrumbsBackground: Color.transparent.toString() }, + showPlaceholder: false + })); + this._register(this.breadcrumbsControlFactory.onDidEnablementChange(() => this.handleBreadcrumbsEnablementChange())); titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl)); this._register(toDisposable(() => titleContainer.classList.remove('breadcrumbs'))); // important to remove because the container is a shared dom node @@ -130,19 +141,21 @@ export class NoTabsTitleControl extends TitleControl { setTimeout(() => this.quickInputService.quickAccess.show(), 50); } - openEditor(editor: EditorInput): void { - this.doHandleOpenEditor(); + openEditor(editor: EditorInput): boolean { + return this.doHandleOpenEditor(); } - openEditors(editors: EditorInput[]): void { - this.doHandleOpenEditor(); + openEditors(editors: EditorInput[]): boolean { + return this.doHandleOpenEditor(); } - private doHandleOpenEditor(): void { + private doHandleOpenEditor(): boolean { const activeEditorChanged = this.ifActiveEditorChanged(() => this.redraw()); if (!activeEditorChanged) { this.ifActiveEditorPropertiesChanged(() => this.redraw()); } + + return activeEditorChanged; } beforeCloseEditor(editor: EditorInput): void { @@ -348,16 +361,13 @@ export class NoTabsTitleControl extends TitleControl { } } - getHeight(): IEditorGroupTitleHeight { - return { - total: this.titleHeight, - offset: 0 - }; + getHeight(): number { + return this.tabHeight; } - layout(dimensions: ITitleControlDimensions): Dimension { + layout(dimensions: IEditorTitleControlDimensions): Dimension { this.breadcrumbsControl?.layout(undefined); - return new Dimension(dimensions.container.width, this.getHeight().total); + return new Dimension(dimensions.container.width, this.getHeight()); } } diff --git a/src/vs/workbench/browser/parts/editor/tabFocus.ts b/src/vs/workbench/browser/parts/editor/tabFocus.ts deleted file mode 100644 index 79ad04a21dd31..0000000000000 --- a/src/vs/workbench/browser/parts/editor/tabFocus.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { TabFocus } from 'vs/editor/browser/config/tabFocus'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; - -export class TabFocusMode extends Disposable { - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - - constructor(@IConfigurationService configurationService: IConfigurationService) { - super(); - TabFocus.onDidChangeTabFocus((tabFocusMode) => this._onDidChange.fire(tabFocusMode)); - const editorConfig: boolean = configurationService.getValue('editor.tabFocusMode'); - TabFocus.setTabFocusMode(editorConfig); - this._onDidChange.fire(editorConfig); - this._register(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.tabFocusMode')) { - const value: boolean = configurationService.getValue('editor.tabFocusMode'); - TabFocus.setTabFocusMode(value); - this._onDidChange.fire(value); - } - })); - } -} diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 82d9a3dd1d636..1306b61700957 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -8,21 +8,18 @@ import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { HiddenItemStrategy, MenuWorkbenchToolBar, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import * as colors from 'vs/platform/theme/common/colorRegistry'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; -import { MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_FOREGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; export class CommandCenterControl { @@ -50,21 +47,101 @@ export class CommandCenterControl { }, telemetrySource: 'commandCenter', actionViewItemProvider: (action) => { + if (action instanceof SubmenuItemAction && action.item.submenu === MenuId.CommandCenterCenter) { + return instantiationService.createInstance(CommandCenterCenterViewItem, action, windowTitle, hoverDelegate, {}); + } else { + return createActionViewItem(instantiationService, action, { hoverDelegate }); + } + } + }); + + this._disposables.add(quickInputService.onShow(this._setVisibility.bind(this, false))); + this._disposables.add(quickInputService.onHide(this._setVisibility.bind(this, true))); + this._disposables.add(titleToolbar); + } + + private _setVisibility(show: boolean): void { + this.element.classList.toggle('hide', !show); + this._onDidChangeVisibility.fire(); + } + + dispose(): void { + this._disposables.dispose(); + } +} + + +class CommandCenterCenterViewItem extends BaseActionViewItem { + + private static readonly _quickOpenCommandId = 'workbench.action.quickOpenWithModes'; + + constructor( + private readonly _submenu: SubmenuItemAction, + private readonly _windowTitle: WindowTitle, + private readonly _hoverDelegate: IHoverDelegate, + options: IBaseActionViewItemOptions, + @IKeybindingService private _keybindingService: IKeybindingService, + @IInstantiationService private _instaService: IInstantiationService, + ) { + super(undefined, _submenu.actions[0], options); + } + + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('command-center-center'); + container.classList.toggle('multiple', (this._submenu.actions.length > 1)); + + const hover = this._store.add(setupCustomHover(this._hoverDelegate, container, this.getTooltip())); + + // update label & tooltip when window title changes + this._store.add(this._windowTitle.onDidChange(() => { + hover.update(this.getTooltip()); + })); + + const groups: (readonly IAction[])[] = []; + for (const action of this._submenu.actions) { + if (action instanceof SubmenuAction) { + groups.push(action.actions); + } else { + groups.push([action]); + } + } - if (action instanceof MenuItemAction && action.id === 'workbench.action.quickOpenWithModes') { - class CommandCenterViewItem extends BaseActionViewItem { + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; - constructor(action: IAction, options: IBaseActionViewItemOptions) { + // nested toolbar + const toolbar = this._instaService.createInstance(WorkbenchToolBar, container, { + hiddenItemStrategy: HiddenItemStrategy.NoHide, + telemetrySource: 'commandCenterCenter', + actionViewItemProvider: (action, options) => { + options = { + ...options, + hoverDelegate: this._hoverDelegate, + }; + + if (action.id !== CommandCenterCenterViewItem._quickOpenCommandId) { + return createActionViewItem(this._instaService, action, options); + } + + const that = this; + + return this._instaService.createInstance(class CommandCenterQuickPickItem extends BaseActionViewItem { + + constructor() { super(undefined, action, options); } override render(container: HTMLElement): void { super.render(container); - container.classList.add('command-center'); + container.classList.toggle('command-center-quick-pick'); + + const action = this.action; // icon (search) - const searchIcon = renderIcon(Codicon.search); + const searchIcon = document.createElement('span'); + searchIcon.className = action.class ?? ''; searchIcon.classList.add('search-icon'); // label: just workspace name and optional decorations @@ -74,18 +151,22 @@ export class CommandCenterControl { labelElement.innerText = label; reset(container, searchIcon, labelElement); - const hover = this._store.add(setupCustomHover(hoverDelegate, container, this.getTooltip())); + const hover = this._store.add(setupCustomHover(that._hoverDelegate, container, this.getTooltip())); // update label & tooltip when window title changes - this._store.add(windowTitle.onDidChange(() => { + this._store.add(that._windowTitle.onDidChange(() => { hover.update(this.getTooltip()); labelElement.innerText = this._getLabel(); })); } + protected override getTooltip() { + return that.getTooltip(); + } + private _getLabel(): string { - const { prefix, suffix } = windowTitle.getTitleDecorations(); - let label = windowTitle.isCustomTitleFormat() ? windowTitle.getWindowTitle() : windowTitle.workspaceName; + const { prefix, suffix } = that._windowTitle.getTitleDecorations(); + let label = that._windowTitle.isCustomTitleFormat() ? that._windowTitle.getWindowTitle() : that._windowTitle.workspaceName; if (!label) { label = localize('label.dfl', "Search"); } @@ -97,93 +178,36 @@ export class CommandCenterControl { } return label; } - - protected override getTooltip() { - - // tooltip: full windowTitle - const kb = keybindingService.lookupKeybinding(action.id)?.getLabel(); - const title = kb - ? localize('title', "Search {0} ({1}) \u2014 {2}", windowTitle.workspaceName, kb, windowTitle.value) - : localize('title2', "Search {0} \u2014 {1}", windowTitle.workspaceName, windowTitle.value); - - return title; - } - } - - return instantiationService.createInstance(CommandCenterViewItem, action, {}); - - } else { - return createActionViewItem(instantiationService, action, { hoverDelegate }); + }); } + }); + toolbar.setActions(group); + this._store.add(toolbar); + + // spacer + if (i < groups.length - 1) { + const icon = renderIcon(Codicon.circleSmallFilled); + icon.classList.add('spacer'); + container.appendChild(icon); } - }); - - this._disposables.add(quickInputService.onShow(this._setVisibility.bind(this, false))); - this._disposables.add(quickInputService.onHide(this._setVisibility.bind(this, true))); - this._disposables.add(titleToolbar); + } } - private _setVisibility(show: boolean): void { - this.element.classList.toggle('hide', !show); - this._onDidChangeVisibility.fire(); - } + protected override getTooltip() { - dispose(): void { - this._disposables.dispose(); + // tooltip: full windowTitle + const kb = this._keybindingService.lookupKeybinding(this.action.id)?.getLabel(); + const title = kb + ? localize('title', "Search {0} ({1}) \u2014 {2}", this._windowTitle.workspaceName, kb, this._windowTitle.value) + : localize('title2', "Search {0} \u2014 {1}", this._windowTitle.workspaceName, this._windowTitle.value); + + return title; } } - -// --- theme colors - -// foreground (inactive and active) -colors.registerColor( - 'commandCenter.foreground', - { dark: TITLE_BAR_ACTIVE_FOREGROUND, hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: TITLE_BAR_ACTIVE_FOREGROUND, hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, - localize('commandCenter-foreground', "Foreground color of the command center"), - false -); -colors.registerColor( - 'commandCenter.activeForeground', - { dark: MENUBAR_SELECTION_FOREGROUND, hcDark: MENUBAR_SELECTION_FOREGROUND, light: MENUBAR_SELECTION_FOREGROUND, hcLight: MENUBAR_SELECTION_FOREGROUND }, - localize('commandCenter-activeForeground', "Active foreground color of the command center"), - false -); -colors.registerColor( - 'commandCenter.inactiveForeground', - { dark: TITLE_BAR_INACTIVE_FOREGROUND, hcDark: TITLE_BAR_INACTIVE_FOREGROUND, light: TITLE_BAR_INACTIVE_FOREGROUND, hcLight: TITLE_BAR_INACTIVE_FOREGROUND }, - localize('commandCenter-inactiveForeground', "Foreground color of the command center when the window is inactive"), - false -); -// background (inactive and active) -colors.registerColor( - 'commandCenter.background', - { - dark: Color.white.transparent(0.05), hcDark: null, light: Color.black.transparent(0.05), hcLight: null - }, - localize('commandCenter-background', "Background color of the command center"), - false -); -colors.registerColor( - 'commandCenter.activeBackground', - { dark: Color.white.transparent(0.08), hcDark: MENUBAR_SELECTION_BACKGROUND, light: Color.black.transparent(0.08), hcLight: MENUBAR_SELECTION_BACKGROUND }, - localize('commandCenter-activeBackground', "Active background color of the command center"), - false -); -// border: active and inactive. defaults to active background -colors.registerColor( - 'commandCenter.border', { dark: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcDark: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60), light: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcLight: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60) }, - localize('commandCenter-border', "Border color of the command center"), - false -); -colors.registerColor( - 'commandCenter.activeBorder', { dark: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, - localize('commandCenter-activeBorder', "Active border color of the command center"), - false -); -// border: defaults to active background -colors.registerColor( - 'commandCenter.inactiveBorder', { dark: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcDark: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), light: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcLight: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25) }, - localize('commandCenter-inactiveBorder', "Border color of the command center when the window is inactive"), - false -); +MenuRegistry.appendMenuItem(MenuId.CommandCenter, { + submenu: MenuId.CommandCenterCenter, + title: localize('title3', "Command Center"), + icon: Codicon.shield, + order: 101, +}); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 315429f1f851d..b0c29642ec228 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -134,19 +134,19 @@ visibility: hidden; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item > .action-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-activeForeground); } -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item > .action-label { +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-inactiveForeground); } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .codicon { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: inherit; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { display: flex; align-items: stretch; color: var(--vscode-commandCenter-foreground); @@ -163,27 +163,37 @@ max-width: 600px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center:only-child { - margin-left: 0; /* no margin if there is only the command center, without nav buttons */ -} - -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center { - color: var(--vscode-titleBar-inactiveForeground); - border-color: var(--vscode-commandCenter-inactiveBorder) !important; +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick { + display: flex; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center .search-icon { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick .search-icon { font-size: 14px; opacity: .8; margin: auto 3px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center .search-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick .search-label { overflow: hidden; text-overflow: ellipsis; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center:HOVER { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple .spacer { + height: 100%; + padding: 0 6px; + opacity: 0.5; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:only-child { + margin-left: 0; /* no margin if there is only the command center, without nav buttons */ +} + +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { + color: var(--vscode-titleBar-inactiveForeground); + border-color: var(--vscode-commandCenter-inactiveBorder) !important; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:HOVER { color: var(--vscode-commandCenter-activeForeground); background-color: var(--vscode-commandCenter-activeBackground); border-color: var(--vscode-commandCenter-activeBorder); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 22fa7481b123f..b15e293042560 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -19,7 +19,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb, isNative, platformLocale } from 'vs/base/common/platform'; import { Color } from 'vs/base/common/color'; -import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, prepend, reset } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -77,7 +77,6 @@ export class TitlebarPart extends Part implements ITitleService { private appIconBadge: HTMLElement | undefined; protected menubar?: HTMLElement; protected layoutControls: HTMLElement | undefined; - private layoutToolbar: MenuWorkbenchToolBar | undefined; protected lastLayoutDimensions: Dimension | undefined; private hoverDelegate: IHoverDelegate; @@ -273,18 +272,17 @@ export class TitlebarPart extends Part implements ITitleService { this.title = append(this.centerContent, $('div.window-title')); this.updateTitle(); - if (this.titleBarStyle !== 'native') { this.layoutControls = append(this.rightContent, $('div.layout-controls-container')); this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled); - this.layoutToolbar = this.instantiationService.createInstance(MenuWorkbenchToolBar, this.layoutControls, MenuId.LayoutControlMenu, { + this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, this.layoutControls, MenuId.LayoutControlMenu, { contextMenu: MenuId.TitleBarContext, toolbarOptions: { primaryGroup: () => true }, actionViewItemProvider: action => { return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate }); } - }); + })); } let primaryControlLocation = isMacintosh ? 'left' : 'right'; @@ -310,29 +308,6 @@ export class TitlebarPart extends Part implements ITitleService { })); }); - // Since the title area is used to drag the window, we do not want to steal focus from the - // currently active element. So we restore focus after a timeout back to where it was. - this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => { - if (e.target && this.menubar && isAncestor(e.target as HTMLElement, this.menubar)) { - return; - } - - if (e.target && this.layoutToolbar && isAncestor(e.target as HTMLElement, this.layoutToolbar.getElement())) { - return; - } - - if (e.target && isAncestor(e.target as HTMLElement, this.title)) { - return; - } - - const active = document.activeElement; - setTimeout(() => { - if (active instanceof HTMLElement) { - active.focus(); - } - }, 0 /* need a timeout because we are in capture phase */); - }, true /* use capture to know the currently active element properly */)); - this.updateStyles(); const that = this; @@ -347,7 +322,7 @@ export class TitlebarPart extends Part implements ITitleService { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(): void { if (that.customMenubar) { that.customMenubar.toggleFocus(); } else { diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index a4a46546750a9..9d31beaefc272 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -875,6 +875,59 @@ export const MENUBAR_SELECTION_BORDER = registerColor('menubar.selectionBorder', hcLight: activeContrastBorder, }, localize('menubarSelectionBorder', "Border color of the selected menu item in the menubar.")); +// < --- Command Center --- > + +// foreground (inactive and active) +export const COMMAND_CENTER_FOREGROUND = registerColor( + 'commandCenter.foreground', + { dark: TITLE_BAR_ACTIVE_FOREGROUND, hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: TITLE_BAR_ACTIVE_FOREGROUND, hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, + localize('commandCenter-foreground', "Foreground color of the command center"), + false +); +export const COMMAND_CENTER_ACTIVEFOREGROUND = registerColor( + 'commandCenter.activeForeground', + { dark: MENUBAR_SELECTION_FOREGROUND, hcDark: MENUBAR_SELECTION_FOREGROUND, light: MENUBAR_SELECTION_FOREGROUND, hcLight: MENUBAR_SELECTION_FOREGROUND }, + localize('commandCenter-activeForeground', "Active foreground color of the command center"), + false +); +export const COMMAND_CENTER_INACTIVEFOREGROUND = registerColor( + 'commandCenter.inactiveForeground', + { dark: TITLE_BAR_INACTIVE_FOREGROUND, hcDark: TITLE_BAR_INACTIVE_FOREGROUND, light: TITLE_BAR_INACTIVE_FOREGROUND, hcLight: TITLE_BAR_INACTIVE_FOREGROUND }, + localize('commandCenter-inactiveForeground', "Foreground color of the command center when the window is inactive"), + false +); +// background (inactive and active) +export const COMMAND_CENTER_BACKGROUND = registerColor( + 'commandCenter.background', + { dark: Color.white.transparent(0.05), hcDark: null, light: Color.black.transparent(0.05), hcLight: null }, + localize('commandCenter-background', "Background color of the command center"), + false +); +export const COMMAND_CENTER_ACTIVEBACKGROUND = registerColor( + 'commandCenter.activeBackground', + { dark: Color.white.transparent(0.08), hcDark: MENUBAR_SELECTION_BACKGROUND, light: Color.black.transparent(0.08), hcLight: MENUBAR_SELECTION_BACKGROUND }, + localize('commandCenter-activeBackground', "Active background color of the command center"), + false +); +// border: active and inactive. defaults to active background +export const COMMAND_CENTER_BORDER = registerColor( + 'commandCenter.border', { dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcDark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60), light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcLight: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60) }, + localize('commandCenter-border', "Border color of the command center"), + false +); +export const COMMAND_CENTER_ACTIVEBORDER = registerColor( + 'commandCenter.activeBorder', { dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, + localize('commandCenter-activeBorder', "Active border color of the command center"), + false +); +// border: defaults to active background +export const COMMAND_CENTER_INACTIVEBORDER = registerColor( + 'commandCenter.inactiveBorder', { dark: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcDark: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), light: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcLight: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25) }, + localize('commandCenter-inactiveBorder', "Border color of the command center when the window is inactive"), + false +); + + // < --- Notifications --- > export const NOTIFICATIONS_CENTER_BORDER = registerColor('notificationCenter.border', { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 4725fbf0d9d7d..de7ddcd7c0c73 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -11,6 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution'; import { EditorAccessibilityHelpContribution, HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; +import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/accessibilityStatus'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -23,3 +24,4 @@ const workbenchContributionsRegistry = Registry.as('accessibleViewI export const accessibleViewSupportsNavigation = new RawContextKey('accessibleViewSupportsNavigation', false, true); export const accessibleViewVerbosityEnabled = new RawContextKey('accessibleViewVerbosityEnabled', false, true); export const accessibleViewGoToSymbolSupported = new RawContextKey('accessibleViewGoToSymbolSupported', false, true); +export const accessibleViewOnLastLine = new RawContextKey('accessibleViewOnLastLine', false, true); export const accessibleViewCurrentProviderId = new RawContextKey('accessibleViewCurrentProviderId', undefined, undefined); /** diff --git a/src/vs/workbench/browser/parts/editor/accessibilityStatus.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts similarity index 100% rename from src/vs/workbench/browser/parts/editor/accessibilityStatus.ts rename to src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 1f82f5c1e9b26..bbde0f86f2c70 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -18,6 +18,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; @@ -36,7 +37,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -73,6 +74,9 @@ export interface IAccessibleViewService { previous(): void; goToSymbol(): void; disableHint(): void; + getPosition(): Position | undefined; + setPosition(position: Position, reveal?: boolean): void; + getLastPosition(): Position | undefined; /** * If the setting is enabled, provides the open accessible view hint as a localized string. * @param verbositySettingKey The setting key for the verbosity of the feature @@ -85,6 +89,11 @@ export const enum AccessibleViewType { View = 'view' } +export const enum NavigationType { + Previous = 'previous', + Next = 'next' +} + export interface IAccessibleViewOptions { readMoreUrl?: string; /** @@ -94,10 +103,11 @@ export interface IAccessibleViewOptions { type: AccessibleViewType; } -class AccessibleView extends Disposable { +export class AccessibleView extends Disposable { private _editorWidget: CodeEditorWidget; private _accessiblityHelpIsShown: IContextKey; + private _onLastLine: IContextKey; private _accessibleViewIsShown: IContextKey; private _accessibleViewSupportsNavigation: IContextKey; private _accessibleViewVerbosityEnabled: IContextKey; @@ -132,6 +142,7 @@ class AccessibleView extends Disposable { this._accessibleViewVerbosityEnabled = accessibleViewVerbosityEnabled.bindTo(this._contextKeyService); this._accessibleViewGoToSymbolSupported = accessibleViewGoToSymbolSupported.bindTo(this._contextKeyService); this._accessibleViewCurrentProviderId = accessibleViewCurrentProviderId.bindTo(this._contextKeyService); + this._onLastLine = accessibleViewOnLastLine.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('accessible-view'); @@ -182,6 +193,9 @@ class AccessibleView extends Disposable { } })); this._register(this._editorWidget.onDidDispose(() => this._resetContextKeys())); + this._register(this._editorWidget.onDidChangeCursorPosition(() => { + this._onLastLine.set(this._editorWidget.getPosition()?.lineNumber === this._editorWidget.getModel()?.getLineCount()); + })); } private _resetContextKeys(): void { @@ -242,11 +256,23 @@ class AccessibleView extends Disposable { if (!this._currentProvider || !this._currentContent) { return; } - const tokens = this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown' ? this._currentProvider.getSymbols?.() : marked.lexer(this._currentContent); - if (!tokens) { + const symbols: IAccessibleViewSymbol[] = this._currentProvider.getSymbols?.() || []; + if (symbols?.length) { + return symbols; + } + if (this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown') { + // Symbols haven't been provided and we cannot parse this language + return; + } + const markdownTokens: marked.TokensList | undefined = marked.lexer(this._currentContent); + if (!markdownTokens) { return; } - const symbols: IAccessibleViewSymbol[] = []; + this._convertTokensToSymbols(markdownTokens, symbols); + return symbols.length ? symbols : undefined; + } + + private _convertTokensToSymbols(tokens: marked.TokensList, symbols: IAccessibleViewSymbol[]): void { let firstListItem: string | undefined; for (const token of tokens) { let label: string | undefined = undefined; @@ -267,27 +293,39 @@ class AccessibleView extends Disposable { break; } } - } else { - label = token.label; } if (label) { - symbols.push({ info: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label), firstListItem }); + symbols.push({ markdownToParse: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label), firstListItem }); firstListItem = undefined; } } - return symbols; } showSymbol(provider: IAccessibleContentProvider, symbol: IAccessibleViewSymbol): void { if (!this._currentContent) { return; } - const index = this._currentContent.split('\n').findIndex(line => line.includes(symbol.info.split('\n')[0]) || (symbol.firstListItem && line.includes(symbol.firstListItem))) ?? -1; - if (index >= 0) { - this.show(provider); - this._editorWidget.revealLine(index + 1); - this._editorWidget.setSelection({ startLineNumber: index + 1, startColumn: 1, endLineNumber: index + 1, endColumn: 1 }); + let lineNumber: number | undefined = symbol.lineNumber; + const markdownToParse = symbol.markdownToParse; + if (lineNumber === undefined && markdownToParse === undefined) { + // No symbols provided and we cannot parse this language + return; } + + if (lineNumber === undefined && markdownToParse) { + // Note that this scales poorly, thus isn't used for worst case scenarios like the terminal, for which a line number will always be provided. + // Parse the markdown to find the line number + const index = this._currentContent.split('\n').findIndex(line => line.includes(markdownToParse.split('\n')[0]) || (symbol.firstListItem && line.includes(symbol.firstListItem))) ?? -1; + if (index >= 0) { + lineNumber = index + 1; + } + } + if (lineNumber === undefined) { + return; + } + this.show(provider); + this._editorWidget.revealLine(lineNumber); + this._editorWidget.setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }); this._updateContextKeys(provider, true); } @@ -345,7 +383,7 @@ class AccessibleView extends Disposable { message += '\n'; } } - this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', '\nExit this dialog via the Escape key.'); + this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint; this._updateContextKeys(provider, true); this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => { @@ -449,7 +487,7 @@ class AccessibleView extends Disposable { if (!this._currentProvider) { return false; } - return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || !!this._currentProvider.getSymbols; + return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || !!this._currentProvider.getSymbols?.(); } public showAccessibleViewHelp(): void { @@ -544,7 +582,6 @@ export class AccessibleViewService extends Disposable implements IAccessibleView this._accessibleView = this._register(this._instantiationService.createInstance(AccessibleView)); } this._accessibleView.show(provider); - } next(): void { this._accessibleView?.next(); @@ -574,9 +611,22 @@ export class AccessibleViewService extends Disposable implements IAccessibleView showAccessibleViewHelp(): void { this._accessibleView?.showAccessibleViewHelp(); } + getPosition(): Position | undefined { + return this._accessibleView?.editorWidget.getPosition() ?? undefined; + } + getLastPosition(): Position | undefined { + const lastLine = this._accessibleView?.editorWidget.getModel()?.getLineCount(); + return lastLine !== undefined && lastLine > 0 ? new Position(lastLine, 1) : undefined; + } + setPosition(position: Position, reveal?: boolean): void { + const editorWidget = this._accessibleView?.editorWidget; + editorWidget?.setPosition(position); + if (reveal) { + editorWidget?.revealLine(position.lineNumber); + } + } } - class AccessibleViewSymbolQuickPick { constructor(private _accessibleView: AccessibleView, @IQuickInputService private readonly _quickInputService: IQuickInputService) { @@ -612,7 +662,8 @@ class AccessibleViewSymbolQuickPick { } } -interface IAccessibleViewSymbol extends IPickerQuickAccessItem { - info: string; +export interface IAccessibleViewSymbol extends IPickerQuickAccessItem { + markdownToParse?: string; firstListItem?: string; + lineNumber?: number; } diff --git a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts index 0d9c1bec94345..0cd7d8981475c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts @@ -47,7 +47,7 @@ export class UnfocusedViewDimmingContribution extends Disposable implements IWor // Text editors rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .monaco-editor { ${filterRule} }`); // Breadcrumbs - rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .tabs-breadcrumbs { ${filterRule} }`); + rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .breadcrumbs-below-tabs { ${filterRule} }`); // Terminal editors rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .terminal-wrapper { ${filterRule} }`); // Settings editor diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 3b9e5b4e32371..987705921bdcd 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -451,9 +451,15 @@ configurationRegistry.registerConfiguration({ default: 'auto' }, 'debug.toolBarLocation': { - enum: ['floating', 'docked', 'hidden'], - markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either `floating` in all views, `docked` in the debug view, or `hidden`."), - default: 'floating' + enum: ['floating', 'docked', 'commandCenter', 'hidden'], + markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either `floating` in all views, `docked` in the debug view, `commandCenter` (requires `{0}`), or `hidden`.", '#window.commandCenter#', '#window.titleBarStyle#'), + default: 'floating', + enumDescriptions: [ + nls.localize('debugToolBar.floating', "Show debug toolbar in all views."), + nls.localize('debugToolBar.docked', "Show debug toolbar only in debug views."), + nls.localize('debugToolBar.commandCenter', "Show debug toolbar in the command center."), + nls.localize('debugToolBar.hidden', "Do not show debug toolbar."), + ] }, 'debug.showInStatusBar': { enum: ['never', 'always', 'onFirstSessionStart'], diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 06c4fa639885a..6cf4c61e3ccea 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -8,37 +8,40 @@ import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { distinct, flatten } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; -import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { visit } from 'vs/base/common/json'; import { setProperty } from 'vs/base/common/jsonEdit'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; +import { assertType, isDefined } from 'vs/base/common/types'; import { Constants } from 'vs/base/common/uint'; +import { URI } from 'vs/base/common/uri'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorOption, IEditorHoverOptions } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/core/wordHelper'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; -import { InlineValueContext } from 'vs/editor/common/languages'; -import { IModelDeltaDecoration, InjectedTextCursorStops, ITextModel } from 'vs/editor/common/model'; +import { InlineValue, InlineValueContext } from 'vs/editor/common/languages'; +import { IModelDeltaDecoration, ITextModel, InjectedTextCursorStops } from 'vs/editor/common/model'; import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import * as nls from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { FloatingEditorClickWidget } from 'vs/workbench/browser/codeeditor'; @@ -787,3 +790,31 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.oldDecorations.clear(); } } + + +CommandsRegistry.registerCommand( + '_executeInlineValueProvider', + async ( + accessor: ServicesAccessor, + uri: URI, + iRange: IRange, + context: InlineValueContext + ): Promise => { + assertType(URI.isUri(uri)); + assertType(Range.isIRange(iRange)); + + if (!context || typeof context.frameId !== 'number' || !Range.isIRange(context.stoppedLocation)) { + throw illegalArgument('context'); + } + + const model = accessor.get(IModelService).getModel(uri); + if (!model) { + throw illegalArgument('uri'); + } + + const range = Range.lift(iRange); + const { inlineValuesProvider } = accessor.get(ILanguageFeaturesService); + const providers = inlineValuesProvider.ordered(model); + const providerResults = await Promise.all(providers.map(provider => provider.provideInlineValues(model, range, context, CancellationToken.None))); + return providerResults.flat().filter(isDefined); + }); diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 7e4ee4af327f3..7c720d874d325 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -37,6 +37,7 @@ import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_ import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Codicon } from 'vs/base/common/codicons'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -102,8 +103,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const toolBarLocation = this.configurationService.getValue('debug').toolBarLocation; if ( state === State.Inactive || - toolBarLocation === 'docked' || - toolBarLocation === 'hidden' || + toolBarLocation !== 'floating' || this.debugService.getModel().getSessions().every(s => s.suppressDebugToolbar) || (state === State.Initializing && this.debugService.initializingOptions?.suppressDebugToolbar) ) { @@ -335,6 +335,15 @@ MenuRegistry.onDidChangeMenu(e => { } }); + +MenuRegistry.appendMenuItem(MenuId.CommandCenterCenter, { + submenu: MenuId.DebugToolBar, + title: 'Debug', + icon: Codicon.debug, + order: 1, + when: ContextKeyExpr.and(CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), ContextKeyExpr.equals('config.debug.toolBarLocation', 'commandCenter')) +}); + registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), undefined, { id: DISCONNECT_ID, title: DISCONNECT_LABEL, icon: icons.debugDisconnect, precondition: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED), }); diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index eff654458ba23..be84ffba9c6cb 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -4,14 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { ColorTransformType, asCssVariable, asCssVariableName, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, State, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { STATUS_BAR_FOREGROUND, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; +import { STATUS_BAR_FOREGROUND, STATUS_BAR_BORDER, COMMAND_CENTER_BACKGROUND } from 'vs/workbench/common/theme'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; + // colors for theming @@ -36,6 +38,18 @@ export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBor hcLight: STATUS_BAR_BORDER }, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window")); +export const COMMAND_CENTER_DEBUGGING_BACKGROUND = registerColor( + 'commandCenter.debuggingBackground', + { + dark: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, + hcDark: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, + light: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, + hcLight: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 } + }, + localize('commandCenter-activeBackground', "Command center background color when a program is being debugged"), + true +); + export class StatusBarColorProvider implements IWorkbenchContribution { private readonly disposables = new DisposableStore(); @@ -63,12 +77,13 @@ export class StatusBarColorProvider implements IWorkbenchContribution { @IDebugService private readonly debugService: IDebugService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStatusbarService private readonly statusbarService: IStatusbarService, + @ILayoutService private readonly layoutService: ILayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { this.debugService.onDidChangeState(this.update, this, this.disposables); this.contextService.onDidChangeWorkbenchState(this.update, this, this.disposables); this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('debug.enableStatusBarColor')) { + if (e.affectsConfiguration('debug.enableStatusBarColor') || e.affectsConfiguration('debug.toolBarLocation')) { this.update(); } }); @@ -76,12 +91,20 @@ export class StatusBarColorProvider implements IWorkbenchContribution { } protected update(): void { - const decorateStatusBar: boolean = this.configurationService.getValue('debug').enableStatusBarColor; - if (!decorateStatusBar) { + const debugConfig = this.configurationService.getValue('debug'); + const isInDebugMode = isStatusbarInDebugMode(this.debugService.state, this.debugService.getModel().getSessions()); + if (!debugConfig.enableStatusBarColor) { this.enabled = false; } else { - this.enabled = isStatusbarInDebugMode(this.debugService.state, this.debugService.getModel().getSessions()); + this.enabled = isInDebugMode; } + + const isInCommandCenter = debugConfig.toolBarLocation === 'commandCenter'; + this.layoutService.container.style.setProperty(asCssVariableName(COMMAND_CENTER_BACKGROUND), isInCommandCenter && isInDebugMode + ? asCssVariable(COMMAND_CENTER_DEBUGGING_BACKGROUND) + : '' + ); + } dispose(): void { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index b5294a9bf4316..3620976fcbc34 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -679,7 +679,7 @@ export interface IDebugConfiguration { openDebug: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak'; openExplorerOnEnd: boolean; inlineValues: boolean | 'auto' | 'on' | 'off'; // boolean for back-compat - toolBarLocation: 'floating' | 'docked' | 'hidden'; + toolBarLocation: 'floating' | 'docked' | 'commandCenter' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; extensionHostDebugAdapter: boolean; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 1d8cacb29850d..69cdbcfca6111 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -543,6 +543,7 @@ export class InlineChatController implements IEditorContribution { this._ctxHasActiveRequest.set(false); this._zone.value.widget.updateProgress(false); this._zone.value.widget.updateInfo(''); + this._zone.value.widget.updateToolbar(true); this._log('request took', sw.elapsed(), this._activeSession.provider.debugName); } @@ -662,7 +663,6 @@ export class InlineChatController implements IEditorContribution { const renderedMarkdown = renderMarkdown(response.raw.message, { inline: true }); this._zone.value.widget.updateStatus(''); this._zone.value.widget.updateMarkdownMessage(renderedMarkdown.element); - this._zone.value.widget.updateToolbar(true); const content = renderedMarkdown.element.textContent; if (content) { status = localize('markdownResponseMessage', "{0}", content); @@ -672,7 +672,6 @@ export class InlineChatController implements IEditorContribution { } else if (response instanceof EditResponse) { // edit response -> complex... this._zone.value.widget.updateMarkdownMessage(undefined); - this._zone.value.widget.updateToolbar(true); const canContinue = this._strategy.checkChanges(response); if (!canContinue) { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelProjection.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelProjection.ts deleted file mode 100644 index 01ee19a1ff934..0000000000000 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelProjection.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ArrayQueue } from 'vs/base/common/arrays'; -import { BugIndicatingError } from 'vs/base/common/errors'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { Position } from 'vs/editor/common/core/position'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; -import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; - -export class TextModelProjection extends Disposable { - private static counter: number = 0; - - public static create( - sourceDocument: ITextModel, - projectionConfiguration: ProjectionConfiguration, - modelService: IModelService - ): TextModelProjection { - const textModel = TextModelProjection.createModelReference( - modelService - ); - return new TextModelProjection(textModel, sourceDocument, { dispose: () => { } }, projectionConfiguration); - } - - public static createForTargetDocument( - sourceDocument: ITextModel, - projectionConfiguration: ProjectionConfiguration, - targetDocument: ITextModel, - ): TextModelProjection { - return new TextModelProjection(targetDocument, sourceDocument, new DisposableStore(), projectionConfiguration); - } - - private static createModelReference( - modelService: IModelService - ): ITextModel { - const uri = URI.from({ - scheme: 'projected-text-model', - path: `/projection${TextModelProjection.counter++}`, - }); - - return modelService.createModel('', null, uri, false); - } - - private currentBlocks: Block[]; - - constructor( - public readonly targetDocument: ITextModel, - private readonly sourceDocument: ITextModel, - disposable: IDisposable, - projectionConfiguration: ProjectionConfiguration - ) { - super(); - - this._register(disposable); - - const result = getBlocks(sourceDocument, projectionConfiguration); - this.currentBlocks = result.blocks; - targetDocument.setValue(result.transformedContent); - - this._register( - sourceDocument.onDidChangeContent((c) => { - // TODO improve this - const result = getBlocks(sourceDocument, projectionConfiguration); - this.currentBlocks = result.blocks; - targetDocument.setValue(result.transformedContent); - }) - ); - } - - /** - * The created transformer can only be called with monotonically increasing positions. - */ - createMonotonousReverseTransformer(): Transformer { - let lineDelta = 0; - const blockQueue = new ArrayQueue(this.currentBlocks); - let lastLineNumber = 0; - const sourceDocument = this.sourceDocument; - return { - transform(position) { - if (position.lineNumber < lastLineNumber) { - throw new BugIndicatingError(); - } - lastLineNumber = position.lineNumber; - - while (true) { - const next = blockQueue.peek(); - if (!next) { - break; - } - if (position.lineNumber + lineDelta > next.lineRange.startLineNumber) { - blockQueue.dequeue(); - lineDelta += next.lineRange.lineCount - 1; - } else if (position.lineNumber + lineDelta === next.lineRange.startLineNumber && position.column === 2) { - const targetLineNumber = position.lineNumber + lineDelta + next.lineRange.lineCount - 1; - return new Position(targetLineNumber, sourceDocument.getLineMaxColumn(targetLineNumber)); - } else { - break; - } - } - - // Column number never changes - return new Position(position.lineNumber + lineDelta, position.column); - }, - }; - } -} - -function getBlocks(document: ITextModel, configuration: ProjectionConfiguration): { blocks: Block[]; transformedContent: string } { - const blocks: Block[] = []; - const transformedContent: string[] = []; - - let inBlock = false; - let startLineNumber = -1; - let curLine = 0; - - for (const line of document.getLinesContent()) { - curLine++; - if (!inBlock) { - if (line.startsWith(configuration.blockToRemoveStartLinePrefix)) { - inBlock = true; - startLineNumber = curLine; - } else { - transformedContent.push(line); - } - } else { - if (line.startsWith(configuration.blockToRemoveEndLinePrefix)) { - inBlock = false; - blocks.push(new Block(new LineRange(startLineNumber, curLine - startLineNumber + 1))); - // We add a (hopefully) unique symbol so that diffing recognizes the deleted block (HEXAGRAM FOR CONFLICT) - // allow-any-unicode-next-line - transformedContent.push('䷅'); - } - } - } - - return { - blocks, - transformedContent: transformedContent.join('\n') - }; -} - -class Block { - constructor(public readonly lineRange: LineRange) { } -} - -interface ProjectionConfiguration { - blockToRemoveStartLinePrefix: string; - blockToRemoveEndLinePrefix: string; -} - -interface Transformer { - transform(position: Position): Position; -} diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/projection.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/projection.test.ts deleted file mode 100644 index 1020861d913f7..0000000000000 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/projection.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { Position } from 'vs/editor/common/core/position'; -import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { TextModelProjection } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelProjection'; - -suite('TextModelProjection', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('Basic', () => { - const source = createTextModel(` -1 this.container.appendChild(this.labelContainer); -2 -3 // Beak Container -4 this.beakContainer = document.createElement('div'); -<<<<<<< .\input1.ts -this.beakContainer.className = 'status-bar-item-beak-container'; -======= -this.beakContainer.className = 'status-bar-beak-container'; - -// Add to parent ->>>>>>> .\input2.ts -5 this.container.appendChild(this.beakContainer); -6 -7 this.update(entry); -`); - const target = createTextModel(''); - - const projection = TextModelProjection.createForTargetDocument(source, { blockToRemoveStartLinePrefix: '<<<<<<<', blockToRemoveEndLinePrefix: '>>>>>>>' }, target); - - assert.deepStrictEqual(target.getValue(), ` -1 this.container.appendChild(this.labelContainer); -2 -3 // Beak Container -4 this.beakContainer = document.createElement('div'); -䷅ -5 this.container.appendChild(this.beakContainer); -6 -7 this.update(entry); -`); - - const transformer = projection.createMonotonousReverseTransformer(); - const lineNumbers = target.getLinesContent().map((l, idx) => idx + 1); - const transformedLineNumbers = lineNumbers.map(n => transformer.transform(new Position(n, 1))); - - assert.deepStrictEqual(transformedLineNumbers.map(l => l.lineNumber), [ - 1, - 2, - 3, - 4, - 5, - 6, - 13, - 14, - 15, - 16, - ]); - - projection.dispose(); - source.dispose(); - target.dispose(); - }); -}); diff --git a/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts b/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts index 2de6c8fcf3c6e..357927dbf609d 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts @@ -13,8 +13,11 @@ import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextBuffer, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('CellOperations', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Move cells - single cell', async function () { await withTestNotebook( [ @@ -61,8 +64,8 @@ suite('CellOperations', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { - const foldingModel = new FoldingModel(); + async (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); updateFoldingStateAtIndex(foldingModel, 1, true); @@ -144,8 +147,8 @@ suite('CellOperations', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { - const foldingModel = new FoldingModel(); + async (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); updateFoldingStateAtIndex(foldingModel, 1, true); @@ -536,7 +539,7 @@ suite('CellOperations', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); const insertedCellAbove = insertCell(languageService, editor, 4, CellKind.Code, 'above', 'var a = 0;'); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts index b37d157699ba7..71a5b40715cbc 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts @@ -26,7 +26,7 @@ suite('Notebook Statusbar', () => { ['var b = 1;', 'javascript', CellKind.Code, [], {}], ['# header a', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const cellStatusbarSvc = accessor.get(INotebookCellStatusBarService); testDisposables.add(accessor.createInstance(ContributedStatusBarItemController, editor)); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts index b941e70675e6f..0154bb69eb339 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts @@ -55,7 +55,7 @@ suite('Notebook Find', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); @@ -101,7 +101,7 @@ suite('Notebook Find', () => { ['paragraph 1.3', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); @@ -152,7 +152,7 @@ suite('Notebook Find', () => { ['paragraph 1.3', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); @@ -194,7 +194,7 @@ suite('Notebook Find', () => { ['paragraph 1.3', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); @@ -232,7 +232,7 @@ suite('Notebook Find', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts index 43d96a3990da4..541dd70ff09dd 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts @@ -42,7 +42,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } }); const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); @@ -66,7 +66,7 @@ suite('Notebook Clipboard', () => { ['# header d', 'markdown', CellKind.Markup, [], {}], ['var e = 4;', 'javascript', CellKind.Code, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); @@ -97,7 +97,7 @@ suite('Notebook Clipboard', () => { ['# header d', 'markdown', CellKind.Markup, [], {}], ['var e = 4;', 'javascript', CellKind.Code, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); @@ -130,7 +130,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } }); const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); @@ -148,7 +148,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { @@ -182,7 +182,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { let _toCopy: NotebookCellTextModel[] = []; accessor.stub(INotebookService, new class extends mock() { override setToCopy(toCopy: NotebookCellTextModel[]) { _toCopy = toCopy; } @@ -212,7 +212,7 @@ suite('Notebook Clipboard', () => { ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ['paragraph 3', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { @@ -253,7 +253,7 @@ suite('Notebook Clipboard', () => { ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ['paragraph 3', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { @@ -277,7 +277,7 @@ suite('Notebook Clipboard', () => { ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ['paragraph 3', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts index caf0967feb2da..3f1a6f021fa02 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts @@ -16,7 +16,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); assert.strictEqual(viewModel.length, 2); assert.strictEqual(viewModel.getVersionId(), 0); @@ -60,7 +60,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [] @@ -101,7 +101,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [] @@ -133,7 +133,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); const cellList = createNotebookCellList(accessor, new DisposableStore()); cellList.attachViewModel(viewModel); @@ -174,7 +174,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts index c3ec1241ff63b..fb3db12dacec1 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts @@ -11,18 +11,18 @@ import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; suite('NotebookCellList', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - let disposables: DisposableStore; + let testDisposables: DisposableStore; let instantiationService: TestInstantiationService; - setup(() => { - disposables = new DisposableStore(); - instantiationService = setupInstantiationService(disposables); + teardown(() => { + testDisposables.dispose(); }); - teardown(() => { - disposables.dispose(); + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { + testDisposables = new DisposableStore(); + instantiationService = setupInstantiationService(testDisposables); }); test('revealElementsInView: reveal fully visible cell should not scroll', async function () { @@ -34,7 +34,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], cellLineNumberStates: {}, @@ -69,7 +69,7 @@ suite('NotebookCellList', () => { // reveal cell 3, top 200, bottom 300, which is partially visible in the viewport cellList.revealCellsInView({ start: 3, end: 4 }); assert.deepStrictEqual(cellList.scrollTop, 90); - }, disposables); + }); }); test('revealElementsInView: reveal partially visible cell', async function () { @@ -81,7 +81,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -113,7 +113,7 @@ suite('NotebookCellList', () => { // reveal cell 0, top 0, bottom 50 cellList.revealCellsInView({ start: 0, end: 1 }); assert.deepStrictEqual(cellList.scrollTop, 0); - }, disposables); + }); }); test('revealElementsInView: reveal cell out of viewport', async function () { @@ -125,7 +125,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -150,7 +150,7 @@ suite('NotebookCellList', () => { cellList.revealCellsInView({ start: 4, end: 5 }); assert.deepStrictEqual(cellList.scrollTop, 140); // assert.deepStrictEqual(cellList.getViewScrollBottom(), 330); - }, disposables); + }); }); test('updateElementHeight', async function () { @@ -162,7 +162,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -192,7 +192,7 @@ suite('NotebookCellList', () => { cellList.updateElementHeight(0, 80); assert.deepStrictEqual(cellList.scrollTop, 5); - }, disposables); + }); }); test('updateElementHeight with anchor #121723', async function () { @@ -204,7 +204,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -245,7 +245,7 @@ suite('NotebookCellList', () => { cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); assert.deepStrictEqual(cellList.scrollTop, 250 + 100 - cellList.renderHeight); assert.deepStrictEqual(cellList.getViewScrollBottom(), 250 + 100 - cellList.renderHeight + 210); - }, disposables); + }); }); test('updateElementHeight with anchor #121723: focus element out of viewport', async function () { @@ -257,7 +257,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -281,7 +281,7 @@ suite('NotebookCellList', () => { cellList.updateElementHeight2(viewModel.cellAt(1)!, 130); // the focus cell is not in the viewport, the scrolltop should not change at all assert.deepStrictEqual(cellList.scrollTop, 0); - }, disposables); + }); }); test('updateElementHeight of cells out of viewport should not trigger scroll #121140', async function () { @@ -293,7 +293,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -319,14 +319,14 @@ suite('NotebookCellList', () => { cellList.updateElementHeight2(viewModel.cellAt(0)!, 30); assert.deepStrictEqual(cellList.scrollTop, 60); - }, disposables); + }); }); test('visibleRanges should be exclusive of end', async function () { await withTestNotebook( [ ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); @@ -334,7 +334,7 @@ suite('NotebookCellList', () => { cellList.layout(100, 100); assert.deepStrictEqual(cellList.visibleRanges, []); - }, disposables); + }); }); test('visibleRanges should be exclusive of end 2', async function () { @@ -342,7 +342,7 @@ suite('NotebookCellList', () => { [ ['# header a', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false], editorViewStates: [null], @@ -359,6 +359,6 @@ suite('NotebookCellList', () => { cellList.layout(100, 100); assert.deepStrictEqual(cellList.visibleRanges, [{ start: 0, end: 1 }]); - }, disposables); + }); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index 1fa57c44f3143..83d0164b03524 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { CellKind, CellUri, diff, MimeTypeDisplayOrder, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -14,18 +15,18 @@ import { cellIndexesToRanges, cellRangesToIndexes, reduceCellRanges } from 'vs/w import { setupInstantiationService, TestCell } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; suite('NotebookCommon', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let disposables: DisposableStore; let instantiationService: TestInstantiationService; let languageService: ILanguageService; - suiteSetup(() => { + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); languageService = instantiationService.get(ILanguageService); }); - suiteTeardown(() => disposables.dispose()); - test('sortMimeTypes default orders', function () { assert.deepStrictEqual(new MimeTypeDisplayOrder().sort( [ @@ -99,6 +100,8 @@ suite('NotebookCommon', () => { Mimes.text ] ); + + disposables.dispose(); }); @@ -163,6 +166,8 @@ suite('NotebookCommon', () => { Mimes.text ] ); + + disposables.dispose(); }); test('prioritizes mimetypes', () => { @@ -197,6 +202,8 @@ suite('NotebookCommon', () => { const m2 = new MimeTypeDisplayOrder(['a', 'b']); m2.prioritize('b', ['a', 'b', 'a', 'q']); assert.deepStrictEqual(m2.toArray(), ['b', 'a']); + + disposables.dispose(); }); test('sortMimeTypes glob', function () { @@ -224,6 +231,8 @@ suite('NotebookCommon', () => { ], 'glob *' ); + + disposables.dispose(); }); test('diff cells', function () { @@ -231,7 +240,7 @@ suite('NotebookCommon', () => { for (let i = 0; i < 5; i++) { cells.push( - new TestCell('notebook', i, `var a = ${i};`, 'javascript', CellKind.Code, [], languageService) + disposables.add(new TestCell('notebook', i, `var a = ${i};`, 'javascript', CellKind.Code, [], languageService)) ); } @@ -257,8 +266,8 @@ suite('NotebookCommon', () => { ] ); - const cellA = new TestCell('notebook', 6, 'var a = 6;', 'javascript', CellKind.Code, [], languageService); - const cellB = new TestCell('notebook', 7, 'var a = 7;', 'javascript', CellKind.Code, [], languageService); + const cellA = disposables.add(new TestCell('notebook', 6, 'var a = 6;', 'javascript', CellKind.Code, [], languageService)); + const cellB = disposables.add(new TestCell('notebook', 7, 'var a = 7;', 'javascript', CellKind.Code, [], languageService)); const modifiedCells = [ cells[0], @@ -287,7 +296,10 @@ suite('NotebookCommon', () => { } ] ); + + disposables.dispose(); }); + }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts index bc0c116b86ae8..30bf9dbe3a48a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts @@ -32,19 +32,19 @@ class CellSequence implements ISequence { suite('NotebookCommon', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - let disposables: DisposableStore; const configurationService = new TestConfigurationService(); - setup(() => { - disposables = new DisposableStore(); - }); - teardown(() => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { + disposables = new DisposableStore(); + }); + test('diff different source', async () => { await withTestNotebookDiffModel(disposables, [ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts index a4e9d0b225d17..1bd5ca8f03c87 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts @@ -15,20 +15,20 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { DisposableStore } from 'vs/base/common/lifecycle'; suite('ListViewInfoAccessor', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - let disposables: DisposableStore; let instantiationService: TestInstantiationService; + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); }); - teardown(() => { - disposables.dispose(); - }); - test('basics', async function () { await withTestNotebook( [ @@ -38,13 +38,13 @@ suite('ListViewInfoAccessor', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const foldingModel = disposables.add(new FoldingModel()); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); - const listViewInfoAccessor = disposables.add(new ListViewInfoAccessor(cellList)); + const listViewInfoAccessor = ds.add(new ListViewInfoAccessor(cellList)); assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.cellAt(0)!), 0); assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.cellAt(1)!), 1); @@ -79,6 +79,6 @@ suite('ListViewInfoAccessor', () => { assert.deepStrictEqual(expandCellRangesWithHiddenCells(notebookEditor, [{ start: 0, end: 1 }]), [{ start: 0, end: 2 }]); assert.deepStrictEqual(expandCellRangesWithHiddenCells(notebookEditor, [{ start: 2, end: 3 }]), [{ start: 2, end: 5 }]); assert.deepStrictEqual(expandCellRangesWithHiddenCells(notebookEditor, [{ start: 0, end: 1 }, { start: 2, end: 3 }]), [{ start: 0, end: 5 }]); - }, disposables); + }); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 13c2d90875674..8874152bdb927 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -241,7 +241,7 @@ suite('NotebookExecutionStateService', () => { }); }); - test('getExecution and onDidChangeExecution', async function () { + test('getExecution and onDidChangeExecution 2', async function () { return withTestNotebook([], async viewModel => { testNotebookModel = viewModel.notebookDocument; diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts index 11555890b559d..4acfd482bf8bb 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts @@ -10,18 +10,22 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Notebook Folding', () => { let disposables: DisposableStore; let instantiationService: TestInstantiationService; - suiteSetup(() => { + teardown(() => disposables.dispose()); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); instantiationService.spy(IUndoRedoService, 'pushElement'); }); - suiteTeardown(() => disposables.dispose()); test('Folding based on markdown cells', async function () { await withTestNotebook( @@ -34,8 +38,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingController = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingController = ds.add(new FoldingModel()); foldingController.attachViewModel(viewModel); assert.strictEqual(foldingController.regions.findRange(1), 0); @@ -61,8 +65,8 @@ suite('Notebook Folding', () => { ['## header 2.1', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'python', CellKind.Code, [], {}], ], - (editor, viewModel) => { - const foldingController = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingController = ds.add(new FoldingModel()); foldingController.attachViewModel(viewModel); assert.strictEqual(foldingController.regions.findRange(1), 0); @@ -88,8 +92,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingController = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingController = ds.add(new FoldingModel()); foldingController.attachViewModel(viewModel); assert.strictEqual(foldingController.regions.findRange(1), 0); @@ -119,8 +123,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -140,8 +144,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 2, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -162,8 +166,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 2, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -186,8 +190,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -245,8 +249,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([{ start: 2, end: 6 }]); viewModel.updateFoldingRanges(foldingModel.regions); @@ -273,8 +277,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ { start: 5, end: 6 }, @@ -305,8 +309,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ { start: 5, end: 6 }, @@ -339,8 +343,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([{ start: 2, end: 6 }]); viewModel.updateFoldingRanges(foldingModel.regions); @@ -375,8 +379,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ { start: 5, end: 6 }, diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts index ecaef1897ed20..24d459f9eef89 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts @@ -23,15 +23,21 @@ import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/no import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('NotebookKernelHistoryService', () => { - const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + let disposables: DisposableStore; let instantiationService: TestInstantiationService; let kernelService: INotebookKernelService; let onDidAddNotebookDocument: Emitter; - setup(function () { + teardown(() => { + disposables.dispose(); + }); + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(function () { + disposables = new DisposableStore(); onDidAddNotebookDocument = new Emitter(); disposables.add(onDidAddNotebookDocument); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index 35a5b5c8b150f..b1aa61118fdf3 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -25,21 +25,22 @@ suite('NotebookSelection', () => { }); suite('NotebookCellList focus/selection', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - let disposables: DisposableStore; let instantiationService: TestInstantiationService; let languageService: ILanguageService; + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); languageService = instantiationService.get(ILanguageService); }); - teardown(() => { - disposables.dispose(); - }); test('notebook cell list setFocus', async function () { await withTestNotebook( @@ -47,8 +48,8 @@ suite('NotebookCellList focus/selection', () => { ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService, disposables); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); @@ -58,7 +59,7 @@ suite('NotebookCellList focus/selection', () => { cellList.setFocus([1]); assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); cellList.detachViewModel(); - }, disposables); + }); }); test('notebook cell list setSelections', async function () { @@ -67,8 +68,8 @@ suite('NotebookCellList focus/selection', () => { ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService, disposables); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); @@ -79,7 +80,7 @@ suite('NotebookCellList focus/selection', () => { // set selection does not modify focus cellList.setSelection([1]); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); - }, disposables); + }); }); test('notebook cell list setFocus2', async function () { @@ -88,8 +89,8 @@ suite('NotebookCellList focus/selection', () => { ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService, disposables); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); @@ -102,7 +103,7 @@ suite('NotebookCellList focus/selection', () => { cellList.setSelection([1]); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); cellList.detachViewModel(); - }, disposables); + }); }); @@ -115,8 +116,8 @@ suite('NotebookCellList focus/selection', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService, disposables); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); @@ -137,7 +138,7 @@ suite('NotebookCellList focus/selection', () => { cellList.setFocus([3], new KeyboardEvent('keydown'), undefined); assert.deepStrictEqual(viewModel.getFocus(), { start: 3, end: 4 }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 3 }]); - }, disposables); + }); }); @@ -150,11 +151,11 @@ suite('NotebookCellList focus/selection', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - (editor, viewModel) => { - const foldingModel = disposables.add(new FoldingModel()); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = createNotebookCellList(instantiationService, disposables); + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 5); assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); @@ -182,7 +183,7 @@ suite('NotebookCellList focus/selection', () => { cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); assert.strictEqual(cellList.length, 4); assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 }); - }, disposables); + }); }); test('notebook cell list focus/selection with folding regions and applyEdits', async function () { @@ -196,11 +197,11 @@ suite('NotebookCellList focus/selection', () => { ['# header d', 'markdown', CellKind.Markup, [], {}], ['var e = 4;', 'javascript', CellKind.Code, [], {}], ], - (editor, viewModel) => { - const foldingModel = disposables.add(new FoldingModel()); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = createNotebookCellList(instantiationService, disposables); + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); cellList.setFocus([0]); cellList.setSelection([0]); @@ -224,8 +225,8 @@ suite('NotebookCellList focus/selection', () => { // mimic undo editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 0, cells: [ - disposables.add(new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], languageService)), - disposables.add(new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], languageService)) + ds.add(new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], languageService)), + ds.add(new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], languageService)) ] }], true, undefined, () => undefined, undefined, false); viewModel.updateFoldingRanges(foldingModel.regions); @@ -233,9 +234,7 @@ suite('NotebookCellList focus/selection', () => { assert.strictEqual(cellList.getModelIndex2(0), 0); assert.strictEqual(cellList.getModelIndex2(1), 1); assert.strictEqual(cellList.getModelIndex2(2), 2); - - - }, disposables); + }); }); test('notebook cell list getModelIndex', async function () { @@ -247,11 +246,11 @@ suite('NotebookCellList focus/selection', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - (editor, viewModel) => { - const foldingModel = disposables.add(new FoldingModel()); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = createNotebookCellList(instantiationService, disposables); + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); @@ -263,7 +262,7 @@ suite('NotebookCellList focus/selection', () => { assert.deepStrictEqual(cellList.getModelIndex2(0), 0); assert.deepStrictEqual(cellList.getModelIndex2(1), 2); assert.deepStrictEqual(cellList.getModelIndex2(2), 4); - }, disposables); + }); }); @@ -283,7 +282,7 @@ suite('NotebookCellList focus/selection', () => { assert.deepStrictEqual(viewModel.validateRange({ start: -1, end: 1 }), { start: 0, end: 1 }); assert.deepStrictEqual(viewModel.validateRange({ start: 2, end: 1 }), { start: 1, end: 2 }); assert.deepStrictEqual(viewModel.validateRange({ start: 2, end: -1 }), { start: 0, end: 2 }); - }, disposables); + }); }); test('notebook updateSelectionState', async function () { @@ -295,7 +294,7 @@ suite('NotebookCellList focus/selection', () => { (editor, viewModel) => { viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }, { start: -1, end: 0 }] }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); - }, disposables); + }); }); test('notebook cell selection w/ cell deletion', async function () { @@ -310,7 +309,7 @@ suite('NotebookCellList focus/selection', () => { // viewModel.deleteCell(1, true, false); assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); - }, disposables); + }); }); test('notebook cell selection w/ cell deletion from applyEdits', async function () { @@ -330,6 +329,6 @@ suite('NotebookCellList focus/selection', () => { }], true, undefined, () => undefined, undefined, true); assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); - }, disposables); + }); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts index b6efcebd578e3..f80bdb205c441 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts @@ -22,22 +22,22 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; (isWeb ? suite.skip : suite)('NotebookEditorStickyScroll', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - let disposables: DisposableStore; let instantiationService: TestInstantiationService; const domNode: HTMLElement = document.createElement('div'); + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); }); - teardown(() => { - disposables.dispose(); - }); - function getOutline(editor: any) { if (!editor.hasModel()) { assert.ok(false, 'MUST have active text editor'); @@ -103,7 +103,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); test('test1: should render 0->1, visible range 3->8', async function () { @@ -118,7 +118,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], // 300 ['var c = 2;', 'javascript', CellKind.Code, [], {}] // 350 ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 8 }, () => false), editorViewStates: Array.from({ length: 8 }, () => null), @@ -128,7 +128,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); @@ -137,11 +137,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const outline = getOutline(editor); const notebookOutlineEntries = outline.entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); test('test2: should render 0, visible range 6->9 so collapsing next 2 against following section', async function () { @@ -157,7 +157,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], // 350 ['var c = 2;', 'javascript', CellKind.Code, [], {}] // 400 ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 9 }, () => false), editorViewStates: Array.from({ length: 9 }, () => null), @@ -167,7 +167,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); @@ -176,11 +176,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const outline = getOutline(editor); const notebookOutlineEntries = outline.entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); test('test3: should render 0->1, collapsing against equivalent level header', async function () { @@ -197,7 +197,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], // 400 ['var c = 2;', 'javascript', CellKind.Code, [], {}] // 450 ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 10 }, () => false), editorViewStates: Array.from({ length: 10 }, () => null), @@ -207,7 +207,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); @@ -216,11 +216,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const outline = getOutline(editor); const notebookOutlineEntries = outline.entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); // outdated/improper behavior @@ -236,7 +236,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 8 }, () => false), editorViewStates: Array.from({ length: 8 }, () => null), @@ -246,7 +246,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); @@ -255,11 +255,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const outline = getOutline(editor); const notebookOutlineEntries = outline.entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); // outdated/improper behavior @@ -277,7 +277,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 10 }, () => false), editorViewStates: Array.from({ length: 10 }, () => null), @@ -287,7 +287,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); @@ -296,11 +296,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const outline = getOutline(editor); const notebookOutlineEntries = outline.entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); // outdated/improper behavior @@ -318,7 +318,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['### header bbb', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 10 }, () => false), editorViewStates: Array.from({ length: 10 }, () => null), @@ -328,7 +328,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); @@ -337,11 +337,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const outline = getOutline(editor); const notebookOutlineEntries = outline.entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); // waiting on behavior push to fix this. @@ -361,7 +361,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['### header bbb', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 12 }, () => false), editorViewStates: Array.from({ length: 12 }, () => null), @@ -371,7 +371,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); @@ -380,11 +380,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const outline = getOutline(editor); const notebookOutlineEntries = outline.entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); outline.dispose(); - }, disposables); + }); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index b5134c88bdf6d..13dc30523c76b 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -28,8 +28,11 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { NotebookEditorTestModel, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { IBaseCellEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('NotebookViewModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let disposables: DisposableStore; let instantiationService: TestInstantiationService; let textModelService: ITextModelService; @@ -58,9 +61,16 @@ suite('NotebookViewModel', () => { test('ctor', function () { const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService); const model = new NotebookEditorTestModel(notebook); - const viewContext = new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false), new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions)); + const options = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false); + const eventDispatcher = new NotebookEventDispatcher(); + const viewContext = new ViewContext(options, eventDispatcher, () => ({} as IBaseCellEditorOptions)); const viewModel = new NotebookViewModel('notebook', model.notebook, viewContext, null, { isReadOnly: false }, instantiationService, bulkEditService, undoRedoService, textModelService, notebookExecutionStateService); assert.strictEqual(viewModel.viewType, 'notebook'); + notebook.dispose(); + model.dispose(); + options.dispose(); + eventDispatcher.dispose(); + viewModel.dispose(); }); test('insert/delete', async function () { @@ -79,6 +89,9 @@ suite('NotebookViewModel', () => { assert.strictEqual(viewModel.length, 2); assert.strictEqual(viewModel.notebookDocument.cells.length, 2); assert.strictEqual(viewModel.getCellIndex(cell), -1); + + cell.dispose(); + cell.model.dispose(); } ); }); @@ -105,6 +118,11 @@ suite('NotebookViewModel', () => { assert.strictEqual(viewModel.length, 3); assert.strictEqual(viewModel.notebookDocument.cells.length, 3); assert.strictEqual(viewModel.getCellIndex(cell2), 2); + + cell.dispose(); + cell.model.dispose(); + cell2.dispose(); + cell2.model.dispose(); } ); }); @@ -136,6 +154,8 @@ function getVisibleCells(cells: T[], hiddenRanges: ICellRange[]) { } suite('NotebookViewModel Decorations', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('tracking range', async function () { await withTestNotebook( [ @@ -153,7 +173,7 @@ suite('NotebookViewModel Decorations', () => { end: 2, }); - insertCellAtIndex(viewModel, 0, 'var d = 6;', 'javascript', CellKind.Code, {}, [], true, true); + const cell1 = insertCellAtIndex(viewModel, 0, 'var d = 6;', 'javascript', CellKind.Code, {}, [], true, true); assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), { start: 2, @@ -167,7 +187,7 @@ suite('NotebookViewModel Decorations', () => { end: 2 }); - insertCellAtIndex(viewModel, 3, 'var d = 7;', 'javascript', CellKind.Code, {}, [], true, true); + const cell2 = insertCellAtIndex(viewModel, 3, 'var d = 7;', 'javascript', CellKind.Code, {}, [], true, true); assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), { start: 1, @@ -187,6 +207,11 @@ suite('NotebookViewModel Decorations', () => { end: 1 }); + + cell1.dispose(); + cell1.model.dispose(); + cell2.dispose(); + cell2.model.dispose(); } ); }); @@ -268,13 +293,11 @@ suite('NotebookViewModel Decorations', () => { return original.indexOf(a) >= 0; }), [{ start: 1, deleteCount: 1, toInsert: [2, 6] }]); }); - - test('hidden ranges', async function () { - - }); }); suite('NotebookViewModel API', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('#115432, get nearest code cell', async function () { await withTestNotebook( [ diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 64f2c71aaf2c5..d9dc02d5bc59b 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -173,7 +173,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi } } -export function setupInstantiationService(disposables: Pick = new DisposableStore()) { +export function setupInstantiationService(disposables: DisposableStore) { const instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(ILanguageService, disposables.add(new LanguageService())); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); @@ -196,7 +196,7 @@ export function setupInstantiationService(disposables: Pick, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: IActiveNotebookEditorDelegate; viewModel: NotebookViewModel } { +function _createTestNotebookEditor(instantiationService: TestInstantiationService, disposables: DisposableStore, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: IActiveNotebookEditorDelegate; viewModel: NotebookViewModel } { const viewType = 'notebook'; const notebook = disposables.add(instantiationService.createInstance(NotebookTextModel, viewType, URI.parse('test'), cells.map((cell): ICellDto2 => { @@ -339,7 +339,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic return { editor: notebookEditor, viewModel }; } -export function createTestNotebookEditor(instantiationService: TestInstantiationService, disposables: Pick, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: INotebookEditorDelegate; viewModel: NotebookViewModel } { +export function createTestNotebookEditor(instantiationService: TestInstantiationService, disposables: DisposableStore, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: INotebookEditorDelegate; viewModel: NotebookViewModel } { return _createTestNotebookEditor(instantiationService, disposables, cells); } @@ -391,12 +391,13 @@ interface IActiveTestNotebookEditorDelegate extends IActiveNotebookEditorDelegat visibleRanges: ICellRange[]; } -export async function withTestNotebook(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: IActiveTestNotebookEditorDelegate, viewModel: NotebookViewModel, accessor: TestInstantiationService) => Promise | R, disposables: DisposableStore = new DisposableStore(), accessor?: TestInstantiationService): Promise { +export async function withTestNotebook(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: IActiveTestNotebookEditorDelegate, viewModel: NotebookViewModel, disposables: DisposableStore, accessor: TestInstantiationService) => Promise | R, accessor?: TestInstantiationService): Promise { + const disposables: DisposableStore = new DisposableStore(); const instantiationService = accessor ?? setupInstantiationService(disposables); const notebookEditor = _createTestNotebookEditor(instantiationService, disposables, cells); return runWithFakedTimers({ useFakeTimers: true }, async () => { - const res = await callback(notebookEditor.editor, notebookEditor.viewModel, instantiationService); + const res = await callback(notebookEditor.editor, notebookEditor.viewModel, disposables, instantiationService); if (res instanceof Promise) { res.finally(() => { notebookEditor.editor.dispose(); @@ -414,7 +415,7 @@ export async function withTestNotebook(cells: [source: string, lang: st }); } -export function createNotebookCellList(instantiationService: TestInstantiationService, disposables: Pick, viewContext?: ViewContext) { +export function createNotebookCellList(instantiationService: TestInstantiationService, disposables: DisposableStore, viewContext?: ViewContext) { const delegate: IListVirtualDelegate = { getHeight(element: CellViewModel) { return element.getHeight(17); }, getTemplateId() { return 'template'; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index d751cf35bdd53..8c9da329807de 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -37,7 +37,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento, IEditorOpenContext, IEditorPane } from 'vs/workbench/common/editor'; import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; -import { getCommonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; +import { ITOCEntry, getCommonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; @@ -1242,6 +1242,11 @@ export class SettingsEditor2 extends EditorPane { return undefined; } + private refreshModels(resolvedSettingsRoot: ITOCEntry) { + this.settingsTreeModel.update(resolvedSettingsRoot); + this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; + } + private async onConfigUpdate(keys?: ReadonlySet, forceRefresh = false, schemaChange = false): Promise { if (keys && this.settingsTreeModel) { return this.updateElementsByKey(keys); @@ -1340,7 +1345,7 @@ export class SettingsEditor2 extends EditorPane { this.searchResultModel?.updateChildren(); if (this.settingsTreeModel) { - this.settingsTreeModel.update(resolvedSettingsRoot); + this.refreshModels(resolvedSettingsRoot); if (schemaChange && !!this.searchResultModel) { // If an extension's settings were just loaded and a search is active, retrigger the search so it shows up @@ -1351,8 +1356,7 @@ export class SettingsEditor2 extends EditorPane { this.renderTree(undefined, forceRefresh); } else { this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, this.workspaceTrustManagementService.isWorkspaceTrusted()); - this.settingsTreeModel.update(resolvedSettingsRoot); - this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; + this.refreshModels(resolvedSettingsRoot); // Don't restore the cached state if we already have a query value from calling _setOptions(). const cachedState = !this.viewState.query ? this.restoreCachedState() : undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index 687b9ee025a48..4d1479ca9f50a 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -47,7 +47,7 @@ fi # Apply EnvironmentVariableCollections if needed if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then - IFS=':' read -ra ADDR <<< "$VSCODE_ENV_REPLACE" + IFS=':' read -ra ADDR <<<"$VSCODE_ENV_REPLACE" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -56,7 +56,7 @@ if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then builtin unset VSCODE_ENV_REPLACE fi if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then - IFS=':' read -ra ADDR <<< "$VSCODE_ENV_PREPEND" + IFS=':' read -ra ADDR <<<"$VSCODE_ENV_PREPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -65,7 +65,7 @@ if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then builtin unset VSCODE_ENV_PREPEND fi if [ -n "${VSCODE_ENV_APPEND:-}" ]; then - IFS=':' read -ra ADDR <<< "$VSCODE_ENV_APPEND" + IFS=':' read -ra ADDR <<<"$VSCODE_ENV_APPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -95,13 +95,52 @@ __vsc_get_trap() { builtin printf '%s' "${terms[2]:-}" } +__vsc_command_available() { + builtin local trash + trash=$(builtin command -v "$1" 2>&1) + builtin return $? +} + +# We provide two faster escaping functions here. +# The first one escapes each byte 0xab into '\xab', which is most scalable and has promising runtime +# efficiency, except that it relies on external commands od and tr. +# The second one is much faster and has zero dependency, except that it escapes only +# '\\' -> '\\\\' and ';' -> '\x3b' and scales up badly when more patterns are needed. +# We default to use the first function if od and tr are available, and fallback to the second otherwise. +if __vsc_command_available od && __vsc_command_available tr; then + __vsc_escape_value_fast() { + builtin local out + # -An removes line number + # -v do not use * to mark line suppression + # -tx1 prints each byte as two-digit hex + # tr -d '\n' concats all output lines + out=$(od -An -vtx1 <<<"$1" | tr -d '\n') + out=${out// /\\x} + # <<<"$1" prepends a trailing newline already, so we don't need to printf '%s\n' + builtin printf '%s' "${out}" + } +else + __vsc_escape_value_fast() { + builtin local LC_ALL=C out + out=${1//\\/\\\\} + out=${out//;/\\x3b} + builtin printf '%s\n' "${out}" + } +fi + # The property (P) and command (E) codes embed values which require escaping. # Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex. __vsc_escape_value() { + # If the input being too large, switch to the faster function + if [ "${#1}" -ge 2000 ]; then + __vsc_escape_value_fast "$1" + builtin return + fi + # Process text byte by byte, not by codepoint. builtin local LC_ALL=C str="${1}" i byte token out='' - for (( i=0; i < "${#str}"; ++i )); do + for ((i = 0; i < "${#str}"; ++i)); do byte="${str:$i:1}" # Escape backslashes and semi-colons diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 8e58f90c8fcd1..3b01c31dff1e0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -61,6 +61,7 @@ import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/cap import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Iterable } from 'vs/base/common/iterator'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewOnLastLine } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -479,7 +480,7 @@ export function registerTerminalActions() { id: TerminalCommandId.Focus, title: terminalStrings.focus, keybinding: { - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.accessibleBufferOnLastLine), + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, accessibleViewOnLastLine, accessibleViewCurrentProviderId.isEqualTo(AccessibleViewProviderId.Terminal)), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib }, diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 2c67eda18cb9f..efe0d3be971ab 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -399,7 +399,6 @@ export const enum TerminalCommandId { OpenWebLink = 'workbench.action.terminal.openUrlLink', RunRecentCommand = 'workbench.action.terminal.runRecentCommand', FocusAccessibleBuffer = 'workbench.action.terminal.focusAccessibleBuffer', - NavigateAccessibleBuffer = 'workbench.action.terminal.navigateAccessibleBuffer', AccessibleBufferGoToNextCommand = 'workbench.action.terminal.accessibleBufferGoToNextCommand', AccessibleBufferGoToPreviousCommand = 'workbench.action.terminal.accessibleBufferGoToPreviousCommand', CopyLastCommandOutput = 'workbench.action.terminal.copyLastCommandOutput', diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index ce4f7a4168cef..1073cf8767deb 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -49,12 +49,6 @@ export namespace TerminalContextKeys { /** Whether any terminal is focused, including detached terminals used in other UI. */ export const focusInAny = new RawContextKey(TerminalContextKeyStrings.FocusInAny, false, localize('terminalFocusInAnyContextKey', "Whether any terminal is focused, including detached terminals used in other UI.")); - /** Whether the accessible buffer is focused. */ - export const accessibleBufferFocus = new RawContextKey(TerminalContextKeyStrings.AccessibleBufferFocus, false, localize('terminalAccessibleBufferFocusContextKey', "Whether the terminal accessible buffer is focused.")); - - /** Whether the accessible buffer focus is on the last line. */ - export const accessibleBufferOnLastLine = new RawContextKey(TerminalContextKeyStrings.AccessibleBufferOnLastLine, false, localize('terminalAccessibleBufferOnLastLineContextKey', "Whether the accessible buffer focus is on the last line.")); - /** Whether a terminal in the editor area is focused. */ export const editorFocus = new RawContextKey(TerminalContextKeyStrings.EditorFocus, false, localize('terminalEditorFocusContextKey', "Whether a terminal in the editor area is focused.")); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts index 53bc70995a97b..1467877846775 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalLogService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import type { IMarker, Terminal } from 'xterm'; -export class BufferContentTracker { +export class BufferContentTracker extends Disposable { /** * Marks the last part of the buffer that was cached */ @@ -27,6 +28,7 @@ export class BufferContentTracker { private readonly _xterm: Pick & { raw: Terminal }, @ITerminalLogService private readonly _logService: ITerminalLogService, @IConfigurationService private readonly _configurationService: IConfigurationService) { + super(); } reset(): void { @@ -44,7 +46,7 @@ export class BufferContentTracker { this._removeViewportContent(); this._updateCachedContent(); this._updateViewportContent(); - this._lastCachedMarker = this._xterm.raw.registerMarker(); + this._lastCachedMarker = this._register(this._xterm.raw.registerMarker()); this._logService.debug('Buffer content tracker: set ', this._lines.length, ' lines'); } diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index b0596b2d2b05e..29783e7170bd1 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -7,25 +7,27 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { TerminalSettingId, terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; -import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IAccessibleViewService, NavigationType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; import { TerminalAccessibleContentProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp'; -import { AccessibleBufferWidget, NavigationType } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer'; import { TextAreaSyncAddon } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon'; import type { Terminal } from 'xterm'; - +import { Position } from 'vs/editor/common/core/position'; +import { ICommandWithEditorLine, TerminalAccessibleBufferProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider'; class TextAreaSyncContribution extends DisposableStore implements ITerminalContribution { static readonly ID = 'terminal.textAreaSync'; @@ -48,56 +50,113 @@ class TextAreaSyncContribution extends DisposableStore implements ITerminalContr } registerTerminalContribution(TextAreaSyncContribution.ID, TextAreaSyncContribution); -class AccessibleBufferContribution extends DisposableStore implements ITerminalContribution { - static readonly ID = 'terminal.accessible-buffer'; - private _xterm: IXtermTerminal & { raw: Terminal } | undefined; - static get(instance: ITerminalInstance): AccessibleBufferContribution | null { - return instance.getContribution(AccessibleBufferContribution.ID); - } - private _accessibleBufferWidget: AccessibleBufferWidget | undefined; +export class TerminalAccessibleViewContribution extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.accessibleBufferProvider'; + static get(instance: ITerminalInstance): TerminalAccessibleViewContribution | null { + return instance.getContribution(TerminalAccessibleViewContribution.ID); + } + private _bufferTracker: BufferContentTracker | undefined; + private _xterm: Pick & { raw: Terminal } | undefined; constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, widgetManager: TerminalWidgetManager, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService - ) { + @ITerminalService private readonly _terminalService: ITerminalService) { super(); - this.add(_instance.onDidRunText(() => { - const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); - if (focusAfterRun === 'terminal') { - _instance.focus(true); - } else if (focusAfterRun === 'accessible-buffer') { - this.show(); + this._register(AccessibleViewAction.addImplementation(90, 'terminal', () => { + if (this._terminalService.activeInstance !== this._instance) { + return false; } - })); + this.show(); + return true; + }, TerminalContextKeys.focus)); } - layout(xterm: IXtermTerminal & { raw: Terminal }): void { + xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void { + const addon = this._instantiationService.createInstance(TextAreaSyncAddon, this._instance.capabilities); + xterm.raw.loadAddon(addon); + addon.activate(xterm.raw); this._xterm = xterm; } - async show(): Promise { + show(): void { if (!this._xterm) { return; } - if (!this._accessibleBufferWidget) { - this._accessibleBufferWidget = this.add(this._instantiationService.createInstance(AccessibleBufferWidget, this._instance, this._xterm)); + if (!this._bufferTracker) { + this._bufferTracker = this._register(this._instantiationService.createInstance(BufferContentTracker, this._xterm)); } - await this._accessibleBufferWidget.show(); + this._accessibleViewService.show(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker)); + // wait for the render to happen so that the line count is correct and + // the cursor is at the bottom of the buffer + setTimeout(() => { + const lastPosition = this._accessibleViewService.getLastPosition(); + if (lastPosition) { + this._accessibleViewService.setPosition(lastPosition, true); + } + }, 50); } + navigateToCommand(type: NavigationType): void { + const currentLine = this._accessibleViewService.getPosition()?.lineNumber || this._accessibleViewService.getLastPosition()?.lineNumber; + const commands = this._getCommandsWithEditorLine(); + if (!commands?.length || !currentLine) { + return; + } - async createCommandQuickPick(): Promise | undefined> { - return this._accessibleBufferWidget?.createQuickPick(); + const filteredCommands = type === NavigationType.Previous ? commands.filter(c => c.lineNumber < currentLine).sort((a, b) => b.lineNumber - a.lineNumber) : commands.filter(c => c.lineNumber > currentLine).sort((a, b) => a.lineNumber - b.lineNumber); + if (!filteredCommands.length) { + return; + } + this._accessibleViewService.setPosition(new Position(filteredCommands[0].lineNumber, 1), true); } - navigateToCommand(type: NavigationType): void { - return this._accessibleBufferWidget?.navigateToCommand(type); + private _getCommandsWithEditorLine(): ICommandWithEditorLine[] | undefined { + const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); + const commands = capability?.commands; + const currentCommand = capability?.currentCommand; + if (!commands?.length) { + return; + } + const result: ICommandWithEditorLine[] = []; + for (const command of commands) { + const lineNumber = this._getEditorLineForCommand(command); + if (!lineNumber) { + continue; + } + result.push({ command, lineNumber }); + } + if (currentCommand) { + const lineNumber = this._getEditorLineForCommand(currentCommand); + if (!!lineNumber) { + result.push({ command: currentCommand, lineNumber }); + } + } + return result; } - hide(): void { - this._accessibleBufferWidget?.hide(); + + private _getEditorLineForCommand(command: ITerminalCommand | ICurrentPartialCommand): number | undefined { + if (!this._bufferTracker) { + return; + } + let line: number | undefined; + if ('marker' in command) { + line = command.marker?.line; + } else if ('commandStartMarker' in command) { + line = command.commandStartMarker?.line; + } + if (line === undefined || line < 0) { + return; + } + line = this._bufferTracker.bufferToEditorLineMapping.get(line); + if (line === undefined) { + return; + } + return line + 1; } + } -registerTerminalContribution(AccessibleBufferContribution.ID, AccessibleBufferContribution); +registerTerminalContribution(TerminalAccessibleViewContribution.ID, TerminalAccessibleViewContribution); export class TerminalAccessibilityHelpContribution extends Disposable { static ID: 'terminalAccessibilityHelpContribution'; @@ -115,72 +174,51 @@ export class TerminalAccessibilityHelpContribution extends Disposable { return; } accessibleViewService.show(instantiationService.createInstance(TerminalAccessibleContentProvider, instance, terminal)); - }, ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus))); + }, ContextKeyExpr.or(TerminalContextKeys.focus, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))))); } } registerTerminalContribution(TerminalAccessibilityHelpContribution.ID, TerminalAccessibilityHelpContribution); -registerTerminalAction({ - id: TerminalCommandId.FocusAccessibleBuffer, - title: { value: localize('workbench.action.terminal.focusAccessibleBuffer', 'Focus Accessible Buffer'), original: 'Focus Accessible Buffer' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - keybinding: [ - { - primary: KeyMod.Alt | KeyCode.F2, - secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], - linux: { - primary: KeyMod.Alt | KeyCode.F2 | KeyMod.Shift, - secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow] - }, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus, ContextKeyExpr.or(terminalTabFocusModeContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) - } - ], - run: async (c) => { - const instance = await c.service.getActiveOrCreateInstance(); - await c.service.revealActiveTerminal(); - if (!instance) { - return; - } - await AccessibleBufferContribution.get(instance)?.show(); - } -}); -registerTerminalAction({ - id: TerminalCommandId.NavigateAccessibleBuffer, - title: { value: localize('workbench.action.terminal.navigateAccessibleBuffer', 'Navigate Accessible Buffer'), original: 'Navigate Accessible Buffer' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, - weight: KeybindingWeight.WorkbenchContrib + 2, - when: TerminalContextKeys.accessibleBufferFocus - } - ], - run: async (c) => { - const instance = await c.service.getActiveOrCreateInstance(); - await c.service.revealActiveTerminal(); - if (!instance) { +class FocusAccessibleBufferAction extends Action2 { + constructor() { + super({ + id: TerminalCommandId.FocusAccessibleBuffer, + title: { value: localize('workbench.action.terminal.focusAccessibleBuffer', 'Focus Accessible Buffer'), original: 'Focus Accessible Buffer' }, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + keybinding: [ + { + primary: KeyMod.Alt | KeyCode.F2, + secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + linux: { + primary: KeyMod.Alt | KeyCode.F2 | KeyMod.Shift, + secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow] + }, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus) + } + ] + }); + } + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const terminalService = accessor.get(ITerminalService); + const terminal = await terminalService.getActiveOrCreateInstance(); + if (!terminal?.xterm) { return; } - const quickPick = await AccessibleBufferContribution.get(instance)?.createCommandQuickPick(); - quickPick?.show(); + TerminalAccessibleViewContribution.get(terminal)?.show(); } -}); +} +registerAction2(FocusAccessibleBufferAction); registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToNextCommand, title: { value: localize('workbench.action.terminal.accessibleBufferGoToNextCommand', 'Accessible Buffer Go to Next Command'), original: 'Accessible Buffer Go to Next Command' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated, TerminalContextKeys.accessibleBufferFocus), + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - weight: KeybindingWeight.WorkbenchContrib + 2 - }, { primary: KeyMod.Alt | KeyCode.DownArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: ContextKeyExpr.and(ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), weight: KeybindingWeight.WorkbenchContrib + 2 } ], @@ -190,7 +228,7 @@ registerTerminalAction({ if (!instance) { return; } - await AccessibleBufferContribution.get(instance)?.navigateToCommand(NavigationType.Next); + await TerminalAccessibleViewContribution.get(instance)?.navigateToCommand(NavigationType.Next); } }); @@ -198,16 +236,11 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToPreviousCommand, title: { value: localize('workbench.action.terminal.accessibleBufferGoToPreviousCommand', 'Accessible Buffer Go to Previous Command'), original: 'Accessible Buffer Go to Previous Command' }, - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.accessibleBufferFocus), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - weight: KeybindingWeight.WorkbenchContrib + 2 - }, { primary: KeyMod.Alt | KeyCode.UpArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: ContextKeyExpr.and(ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), weight: KeybindingWeight.WorkbenchContrib + 2 } ], @@ -217,6 +250,6 @@ registerTerminalAction({ if (!instance) { return; } - await AccessibleBufferContribution.get(instance)?.navigateToCommand(NavigationType.Previous); + await TerminalAccessibleViewContribution.get(instance)?.navigateToCommand(NavigationType.Previous); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index 340427fd528d0..ec61041471bb9 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -8,15 +8,15 @@ import { format } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShellIntegrationStatus, TerminalSettingId, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import type { Terminal } from 'xterm'; export const enum ClassName { @@ -29,7 +29,8 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc private readonly _hasShellIntegration: boolean = false; onClose() { - if (this._contextKeyService.getContextKeyValue(TerminalContextKeys.accessibleBufferFocus.key) === true) { + const expr = ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal)); + if (expr?.evaluate(this._contextKeyService.getContext(null))) { this._commandService.executeCommand(TerminalCommandId.FocusAccessibleBuffer); } else { this._instance.focus(); @@ -84,7 +85,7 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc shellIntegrationCommandList.push(localize('shellIntegration', "The terminal has a feature called shell integration that offers an enhanced experience and provides useful commands for screen readers such as:")); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToNextCommand, localize('goToNextCommand', 'Go to Next Command ({0})'), localize('goToNextCommandNoKb', 'Go to Next Command is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToPreviousCommand, localize('goToPreviousCommand', 'Go to Previous Command ({0})'), localize('goToPreviousCommandNoKb', 'Go to Previous Command is currently not triggerable by a keybinding.'))); - shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.NavigateAccessibleBuffer, localize('navigateAccessibleBuffer', 'Navigate Accessible Buffer ({0})'), localize('navigateAccessibleBufferNoKb', 'Navigate Accessible Buffer is currently not triggerable by a keybinding.'))); + shellIntegrationCommandList.push('- ' + this._descriptionForCommand(AccessibilityCommandId.GoToSymbol, localize('goToSymbol', 'Go to Symbol ({0})'), localize('goToSymbolNoKb', 'Go to symbol is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.RunRecentCommand, localize('runRecentCommand', 'Run Recent Command ({0})'), localize('runRecentCommandNoKb', 'Run Recent Command is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.GoToRecentDirectory, localize('goToRecentDirectory', 'Go to Recent Directory ({0})'), localize('goToRecentDirectoryNoKb', 'Go to Recent Directory is currently not triggerable by a keybinding.'))); content.push(shellIntegrationCommandList.join('\n')); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts deleted file mode 100644 index 8e6c44210bb8d..0000000000000 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts +++ /dev/null @@ -1,268 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; -import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; -import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; -import { ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; -import { TerminalAccessibleWidget } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget'; -import type { Terminal } from 'xterm'; - -export const enum NavigationType { - Next = 'next', - Previous = 'previous' -} - -interface IAccessibleBufferQuickPickItem extends IQuickPickItem { - lineNumber: number; - exitCode?: number; -} - -export const enum ClassName { - AccessibleBuffer = 'accessible-buffer', - Active = 'active' -} - -export class AccessibleBufferWidget extends TerminalAccessibleWidget { - private _isUpdating: boolean = false; - private _pendingUpdates = 0; - - private _bufferTracker: BufferContentTracker; - - private _cursorPosition: { lineNumber: number; column: number } | undefined; - - constructor( - _instance: Pick, - _xterm: Pick & { raw: Terminal }, - @IInstantiationService _instantiationService: IInstantiationService, - @IModelService _modelService: IModelService, - @IConfigurationService _configurationService: IConfigurationService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, - @IContextKeyService _contextKeyService: IContextKeyService, - @ITerminalLogService private readonly _logService: ITerminalLogService, - @ITerminalService _terminalService: ITerminalService - ) { - super(ClassName.AccessibleBuffer, _instance, _xterm, TerminalContextKeys.accessibleBufferFocus, TerminalContextKeys.accessibleBufferOnLastLine, _instantiationService, _modelService, _configurationService, _contextKeyService, _terminalService); - this._bufferTracker = _instantiationService.createInstance(BufferContentTracker, _xterm); - this.element.ariaRoleDescription = localize('terminal.integrated.accessibleBuffer', 'Terminal buffer'); - _instance.onDidRequestFocus(() => this.hide(true)); - this.updateEditor(); - // xterm's initial layout call has already happened - this.layout(); - } - - navigateToCommand(type: NavigationType): void { - const currentLine = this.editorWidget.getPosition()?.lineNumber || this._getDefaultCursorPosition()?.lineNumber; - const commands = this._getCommandsWithEditorLine(); - if (!commands?.length || !currentLine) { - return; - } - - const filteredCommands = type === NavigationType.Previous ? commands.filter(c => c.lineNumber < currentLine).sort((a, b) => b.lineNumber - a.lineNumber) : commands.filter(c => c.lineNumber > currentLine).sort((a, b) => a.lineNumber - b.lineNumber); - if (!filteredCommands.length) { - return; - } - this._cursorPosition = { lineNumber: filteredCommands[0].lineNumber, column: 1 }; - this._resetPosition(); - } - - private _getEditorLineForCommand(command: ITerminalCommand | ICurrentPartialCommand): number | undefined { - let line: number | undefined; - if ('marker' in command) { - line = command.marker?.line; - } else if ('commandStartMarker' in command) { - line = command.commandStartMarker?.line; - } - if (line === undefined || line < 0) { - return; - } - line = this._bufferTracker.bufferToEditorLineMapping.get(line); - if (line === undefined) { - return; - } - return line + 1; - } - - private _getCommandsWithEditorLine(): ICommandWithEditorLine[] | undefined { - const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); - const commands = capability?.commands; - const currentCommand = capability?.currentCommand; - if (!commands?.length) { - return; - } - const result: ICommandWithEditorLine[] = []; - for (const command of commands) { - const lineNumber = this._getEditorLineForCommand(command); - if (!lineNumber) { - continue; - } - result.push({ command, lineNumber }); - } - if (currentCommand) { - const lineNumber = this._getEditorLineForCommand(currentCommand); - if (!!lineNumber) { - result.push({ command: currentCommand, lineNumber }); - } - } - return result; - } - - async createQuickPick(): Promise | undefined> { - this._cursorPosition = this.editorWidget.getPosition() ?? undefined; - const commands = this._getCommandsWithEditorLine(); - if (!commands) { - return; - } - const quickPickItems: IAccessibleBufferQuickPickItem[] = []; - for (const { command, lineNumber } of commands) { - const line = this._getEditorLineForCommand(command); - if (!line) { - continue; - } - quickPickItems.push( - { - label: localize('terminal.integrated.symbolQuickPick.labelNoExitCode', '{0}', command.command), - lineNumber, - exitCode: 'exitCode' in command ? command.exitCode : undefined - }); - } - const quickPick = this._quickInputService.createQuickPick(); - quickPick.canSelectMany = false; - quickPick.onDidChangeActive(() => { - const activeItem = quickPick.activeItems[0]; - if (!activeItem) { - return; - } - if (activeItem.exitCode) { - this._audioCueService.playAudioCue(AudioCue.error, { allowManyInParallel: true, source: 'accessibleBufferWidget' }); - } - this.editorWidget.revealLine(activeItem.lineNumber, 0); - }); - quickPick.onDidHide(() => { - this._resetPosition(); - quickPick.dispose(); - }); - quickPick.onDidAccept(() => { - const item = quickPick.activeItems[0]; - const model = this.editorWidget.getModel(); - if (!model) { - return; - } - if (!item && this._cursorPosition) { - this._resetPosition(); - } else { - this._cursorPosition = { lineNumber: item.lineNumber, column: 1 }; - } - quickPick.dispose(); - this.editorWidget.focus(); - return; - }); - quickPick.items = quickPickItems.reverse(); - return quickPick; - } - - private _resetPosition(): void { - this._cursorPosition = this._cursorPosition ?? this._getDefaultCursorPosition(); - if (!this._cursorPosition) { - return; - } - this.editorWidget.setPosition(this._cursorPosition); - this.editorWidget.setScrollPosition({ scrollTop: this.editorWidget.getTopForLineNumber(this._cursorPosition.lineNumber) }); - } - - override layout(): void { - if (this._bufferTracker) { - this._bufferTracker.reset(); - } - super.layout(); - } - - async updateEditor(dataChanged?: boolean): Promise { - if (this._isUpdating) { - this._pendingUpdates++; - return; - } - this._isUpdating = true; - const model = await this._updateModel(dataChanged); - if (!model) { - return; - } - this._isUpdating = false; - if (this._pendingUpdates) { - this._logService.debug('TerminalAccessibleBuffer._updateEditor: pending updates', this._pendingUpdates); - this._pendingUpdates--; - await this.updateEditor(dataChanged); - } - } - - override registerListeners(): void { - super.registerListeners(); - this._xterm.raw.onWriteParsed(async () => { - if (this._xterm.raw.buffer.active.baseY === 0) { - await this.updateEditor(true); - } - }); - const onRequestUpdateEditor = Event.latch(this._xterm.raw.onScroll); - this._listeners.push(onRequestUpdateEditor(async () => await this.updateEditor(true))); - } - - private _getDefaultCursorPosition(): { lineNumber: number; column: number } | undefined { - const modelLineCount = this.editorWidget.getModel()?.getLineCount(); - return modelLineCount ? { lineNumber: modelLineCount, column: 1 } : undefined; - } - - private async _updateModel(dataChanged?: boolean): Promise { - const linesBefore = this._bufferTracker.lines.length; - this._bufferTracker.update(); - const linesAfter = this._bufferTracker.lines.length; - const modelChanged = linesBefore !== linesAfter; - - // Save the view state before the update if it was set by the user - let savedViewState: IEditorViewState | undefined; - if (dataChanged) { - savedViewState = this.editorWidget.saveViewState() ?? undefined; - } - - let model = this.editorWidget.getModel(); - const text = this._bufferTracker.lines.join('\n'); - if (model) { - model.setValue(text); - } else { - model = await this.getTextModel(this._instance.resource.with({ fragment: `${ClassName.AccessibleBuffer}-${text}` })); - } - this.editorWidget.setModel(model); - - // If the model changed due to new data, restore the view state - // If the model changed due to a refresh or the cursor is a the top, set to the bottom of the buffer - // Otherwise, don't change the position - const positionTopOfBuffer = this.editorWidget.getPosition()?.lineNumber === 1 && this.editorWidget.getPosition()?.column === 1; - if (savedViewState) { - this.editorWidget.restoreViewState(savedViewState); - } else if (modelChanged || positionTopOfBuffer) { - const defaultPosition = this._getDefaultCursorPosition(); - if (defaultPosition) { - this.editorWidget.setPosition(defaultPosition); - this.editorWidget.setScrollPosition({ scrollTop: this.editorWidget.getTopForLineNumber(defaultPosition.lineNumber) }); - } - } - return model!; - } -} - -interface ICommandWithEditorLine { command: ITerminalCommand | ICurrentPartialCommand; lineNumber: number } - diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts new file mode 100644 index 0000000000000..3cf2dfac3962d --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IXtermTerminal, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; +import type { Terminal } from 'xterm'; +import { Event } from 'vs/base/common/event'; + +export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider { + options: IAccessibleViewOptions = { type: AccessibleViewType.View }; + verbositySettingKey = AccessibilityVerbositySettingId.Terminal; + private _xterm: IXtermTerminal & { raw: Terminal } | undefined; + constructor( + private readonly _instance: Pick, + private _bufferTracker: BufferContentTracker, + @IModelService _modelService: IModelService, + @IConfigurationService _configurationService: IConfigurationService, + @IContextKeyService _contextKeyService: IContextKeyService, + @ITerminalService _terminalService: ITerminalService, + @IConfigurationService configurationService: IConfigurationService, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService + ) { + super(); + this.add(_instance.onDidRunText(() => { + const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); + if (focusAfterRun === 'terminal') { + _instance.focus(true); + } else if (focusAfterRun === 'accessible-buffer') { + _accessibleViewService.show(this); + } + })); + this.registerListeners(); + } + + onClose() { + this._instance.focus(); + } + registerListeners(): void { + if (!this._xterm) { + return; + } + this._xterm.raw.onWriteParsed(async () => { + if (this._xterm!.raw.buffer.active.baseY === 0) { + this._accessibleViewService.show(this); + } + }); + const onRequestUpdateEditor = Event.latch(this._xterm.raw.onScroll); + this.add(onRequestUpdateEditor(() => this._accessibleViewService.show(this))); + } + + provideContent(): string { + this._bufferTracker.update(); + return this._bufferTracker.lines.join('\n'); + } + + getSymbols(): IAccessibleViewSymbol[] { + const commands = this._getCommandsWithEditorLine() ?? []; + const symbols: IAccessibleViewSymbol[] = []; + for (const command of commands) { + const label = command.command.command; + if (label) { + symbols.push({ + label, + lineNumber: command.lineNumber + }); + } + } + return symbols; + } + + private _getCommandsWithEditorLine(): ICommandWithEditorLine[] | undefined { + const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); + const commands = capability?.commands; + const currentCommand = capability?.currentCommand; + if (!commands?.length) { + return; + } + const result: ICommandWithEditorLine[] = []; + for (const command of commands) { + const lineNumber = this._getEditorLineForCommand(command); + if (lineNumber === undefined) { + continue; + } + result.push({ command, lineNumber }); + } + if (currentCommand) { + const lineNumber = this._getEditorLineForCommand(currentCommand); + if (lineNumber !== undefined) { + result.push({ command: currentCommand, lineNumber }); + } + } + return result; + } + private _getEditorLineForCommand(command: ITerminalCommand | ICurrentPartialCommand): number | undefined { + let line: number | undefined; + if ('marker' in command) { + line = command.marker?.line; + } else if ('commandStartMarker' in command) { + line = command.commandStartMarker?.line; + } + if (line === undefined || line < 0) { + return; + } + line = this._bufferTracker.bufferToEditorLineMapping.get(line); + if (line === undefined) { + return; + } + return line + 1; + } +} +export interface ICommandWithEditorLine { command: ITerminalCommand | ICurrentPartialCommand; lineNumber: number } diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts deleted file mode 100644 index 448cd3da0e43a..0000000000000 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { KeyCode } from 'vs/base/common/keyCodes'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import * as dom from 'vs/base/browser/dom'; -import { Event } from 'vs/base/common/event'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; -import type { Terminal } from 'xterm'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; -import { localize } from 'vs/nls'; - -const enum ClassName { - Active = 'active', - Hide = 'hide', - Widget = 'terminal-accessible-widget' -} - -export abstract class TerminalAccessibleWidget extends DisposableStore { - - private _element: HTMLElement; - get element(): HTMLElement { return this._element; } - private _editorWidget: CodeEditorWidget; - protected get editorWidget(): CodeEditorWidget { return this._editorWidget; } - private _editorContainer: HTMLElement; - private _xtermElement: HTMLElement; - - protected _listeners: IDisposable[] = []; - - private readonly _focusedContextKey: IContextKey; - private readonly _focusedLastLineContextKey: IContextKey; - private readonly _focusTracker?: dom.IFocusTracker; - - constructor( - private readonly _className: string, - protected readonly _instance: Pick, - protected readonly _xterm: Pick & { raw: Terminal }, - rawFocusContextKey: RawContextKey, - rawFocusLastLineContextKey: RawContextKey, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IModelService private readonly _modelService: IModelService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IContextKeyService protected readonly _contextKeyService: IContextKeyService, - @ITerminalService private readonly _terminalService: ITerminalService - ) { - super(); - this._xtermElement = _xterm.raw.element!; - this._element = document.createElement('div'); - this._element.setAttribute('role', 'document'); - this._element.classList.add(_className); - this._element.classList.add(ClassName.Widget); - this._editorContainer = document.createElement('div'); - const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - contributions: EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeActionController.ID) - }; - const font = _xterm.getFont(); - const editorOptions: IEditorConstructionOptions = { - ...getSimpleEditorOptions(this._configurationService), - lineDecorationsWidth: 6, - dragAndDrop: true, - cursorWidth: 1, - fontSize: font.fontSize, - lineHeight: font.charHeight ? font.charHeight * font.lineHeight : 1, - letterSpacing: font.letterSpacing, - fontFamily: font.fontFamily, - wrappingStrategy: 'advanced', - wrappingIndent: 'none', - padding: { top: 2, bottom: 2 }, - quickSuggestions: false, - renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, - readOnly: true, - ariaLabel: localize('terminalAccessibleBuffer', "Terminal Buffer") - }; - this._editorWidget = this.add(this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions)); - this._element.replaceChildren(this._editorContainer); - this._xtermElement.insertAdjacentElement('beforebegin', this._element); - - this._focusTracker = this.add(dom.trackFocus(this._editorContainer)); - this._focusedContextKey = rawFocusContextKey.bindTo(this._contextKeyService); - this._focusedLastLineContextKey = rawFocusLastLineContextKey.bindTo(this._contextKeyService); - this.add(this._focusTracker.onDidFocus(() => { - this._focusedContextKey?.set(true); - this._focusedLastLineContextKey?.set(this._editorWidget.getSelection()?.positionLineNumber === this._editorWidget.getModel()?.getLineCount()); - })); - this.add(this._focusTracker.onDidBlur(() => { - this._focusedContextKey?.reset(); - this._focusedLastLineContextKey?.reset(); - })); - this._editorWidget.onDidChangeCursorPosition(() => { - console.log(this._editorWidget.getSelection()?.positionLineNumber === this._editorWidget.getModel()?.getLineCount()); - this._focusedLastLineContextKey?.set(this._editorWidget.getSelection()?.positionLineNumber === this._editorWidget.getModel()?.getLineCount()); - }); - - this.add(Event.runAndSubscribe(this._xterm.raw.onResize, () => this.layout())); - this.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectedKeys.has(TerminalSettingId.FontFamily) || e.affectedKeys.has(TerminalSettingId.FontSize) || e.affectedKeys.has(TerminalSettingId.LineHeight) || e.affectedKeys.has(TerminalSettingId.LetterSpacing)) { - const font = this._xterm.getFont(); - this._editorWidget.updateOptions({ fontFamily: font.fontFamily, fontSize: font.fontSize, lineHeight: font.charHeight ? font.charHeight * font.lineHeight : 1, letterSpacing: font.letterSpacing }); - } - })); - this.add(this._editorWidget.onKeyDown((e) => { - switch (e.keyCode) { - case KeyCode.Escape: - // On escape, hide the accessible buffer and force focus onto the terminal - this.hide(true); - break; - } - })); - this.add(this._editorWidget.onDidFocusEditorText(async () => { - this._terminalService.setActiveInstance(this._instance as ITerminalInstance); - this._xtermElement.classList.add(ClassName.Hide); - })); - this.add(this._editorWidget.onDidBlurEditorText(async () => this.hide())); - } - - registerListeners(): void { - this._listeners.push(this._instance.onDidRequestFocus(() => this.editorWidget.focus())); - } - - layout(): void { - this._editorWidget.layout({ width: this._xtermElement.clientWidth, height: this._xtermElement.clientHeight }); - } - - abstract updateEditor(): Promise; - - async show(): Promise { - this.registerListeners(); - await this.updateEditor(); - this.element.tabIndex = -1; - this.layout(); - this.element.classList.add(ClassName.Active); - this._xtermElement.classList.add(ClassName.Hide); - this.editorWidget.focus(); - } - - override dispose(): void { - this._disposeListeners(); - super.dispose(); - } - - private _disposeListeners(): void { - for (const listener of this._listeners) { - listener.dispose(); - } - } - - hide(focusXterm?: boolean): void { - this._disposeListeners(); - this.element.classList.remove(ClassName.Active); - this._xtermElement.classList.remove(ClassName.Hide); - if (focusXterm) { - this._xterm.raw.focus(); - } - } - - async getTextModel(resource: URI): Promise { - const existing = this._modelService.getModel(resource); - if (existing && !existing.isDisposed()) { - return existing; - } - return this._modelService.createModel(`${this._className}-${resource.fragment}`, null, resource, false); - } -} diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index b250ab7263090..5f2a3678c6173 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -64,7 +64,7 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(ILoggerService, store.add(new TestLoggerService())); instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); - instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); capabilities = store.add(new TerminalCapabilityStore()); if (!isWindows) { @@ -75,7 +75,7 @@ suite('Buffer Content Tracker', () => { const container = document.createElement('div'); xterm.raw.open(container); configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${cwd}', description: '${cwd}' } } } }); - bufferTracker = instantiationService.createInstance(BufferContentTracker, xterm); + bufferTracker = store.add(instantiationService.createInstance(BufferContentTracker, xterm)); }); test('should not clear the prompt line', async () => { diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index 5d144353e9ffc..51bd07850d81c 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -10,6 +10,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveInstanceAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; @@ -113,11 +114,16 @@ registerActiveInstanceAction({ f1: true, category, precondition: TerminalContextKeys.terminalHasBeenCreated, - keybinding: { + keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, weight: KeybindingWeight.WorkbenchContrib + 1, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus) + when: TerminalContextKeys.focus + }, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, + weight: KeybindingWeight.WorkbenchContrib + 1, + when: ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal)) }, + ], run: (activeInstance) => TerminalLinkContribution.get(activeInstance)?.showLinkQuickpick() }); registerActiveInstanceAction({ diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 535da4b66c8a3..a860270eca972 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -29,7 +29,7 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITunnelService } from 'vs/platform/tunnel/common/tunnel'; import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping'; -import { parentOriginHash } from 'vs/workbench/browser/iframe'; +import { parentOriginHash } from 'vs/base/browser/iframe'; import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { areWebviewContentOptionsEqual, IWebview, WebviewContentOptions, WebviewExtensionDescription, WebviewInitInfo, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 1efcf6ba71fff..215123550619e 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -725,7 +725,7 @@ export class NativeWindow extends Disposable { // Windows 32-bit warning if (isWindows && this.environmentService.os.arch === 'ia32') { - const message = localize('windows32eolmessage', "{0} on Windows 32-bit will soon stop receiving updates. Consider upgrading to the 64-bit build.", this.productService.nameLong); + const message = localize('windows32eolmessage', "You are running {0} 32-bit, which will soon stop receiving updates on Windows. Consider upgrading to the 64-bit build.", this.productService.nameLong); const actions = [{ label: localize('windowseolBannerLearnMore', "Learn More"), href: 'https://aka.ms/vscode-faq-old-windows' 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 e032eb6b82aa3..14e96886f5d74 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -1579,7 +1579,7 @@ suite('EditorService', () => { test('openEditors() extracts proper resources from untyped editors for workspace trust', async () => { const [, service, accessor] = await createEditorService(); - const input = { resource: URI.parse('my://resource-openEditors') }; + const input = { resource: URI.file('resource-openEditors') }; const otherInput: IResourceDiffEditorInput = { original: { resource: URI.parse('my://resource2-openEditors') }, modified: { resource: URI.parse('my://resource3-openEditors') } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index ad66755797ef9..5ec7f0690c67b 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isLoggingOnly } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { parentOriginHash } from 'vs/workbench/browser/iframe'; +import { parentOriginHash } from 'vs/base/browser/iframe'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { ExtensionHostExitCode, IExtensionHostInitData, MessageType, UIKind, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index f506bbee8112a..f99c067f3a3d4 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -222,6 +222,7 @@ suite('ExtensionService', () => { setup(() => { disposables = new DisposableStore(); + const testProductService = { _serviceBrand: undefined, ...product }; disposables.add(instantiationService = createServices(disposables, [ // custom [IExtensionService, MyTestExtensionService], @@ -235,7 +236,7 @@ suite('ExtensionService', () => { [IExtensionManifestPropertiesService, ExtensionManifestPropertiesService], [IConfigurationService, TestConfigurationService], [IWorkspaceContextService, TestContextService], - [IProductService, { _serviceBrand: undefined, ...product }], + [IProductService, testProductService], [IFileService, TestFileService], [IWorkbenchExtensionEnablementService, TestWorkbenchExtensionEnablementService], [ITelemetryService, NullTelemetryService], @@ -245,7 +246,7 @@ suite('ExtensionService', () => { [IUserDataProfileService, TestUserDataProfileService], [IUriIdentityService, UriIdentityService], [IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService], - [IRemoteAuthorityResolverService, RemoteAuthorityResolverService] + [IRemoteAuthorityResolverService, new RemoteAuthorityResolverService(false, undefined, undefined, testProductService, new NullLogService())] ])); extService = instantiationService.get(IExtensionService); }); diff --git a/src/vs/workbench/test/browser/webview.test.ts b/src/vs/workbench/test/browser/webview.test.ts index 2eac5352c0a43..484be00a657c9 100644 --- a/src/vs/workbench/test/browser/webview.test.ts +++ b/src/vs/workbench/test/browser/webview.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { parentOriginHash } from 'vs/workbench/browser/iframe'; +import { parentOriginHash } from 'vs/base/browser/iframe'; suite('parentOriginHash', () => { diff --git a/src/vs/workbench/test/common/utils.ts b/src/vs/workbench/test/common/utils.ts index f4a175f315ab7..b28eb1d1c8bea 100644 --- a/src/vs/workbench/test/common/utils.ts +++ b/src/vs/workbench/test/common/utils.ts @@ -18,5 +18,5 @@ export function assertCleanState(): void { // If this test fails, it is a clear indication that // your test or suite is leaking services (e.g. via leaking text models) // assert.strictEqual(LanguageService.instanceCount, 0, 'No leaking ILanguageService'); - assert.strictEqual(LanguagesRegistry.instanceCount, 0, 'No leaking LanguagesRegistry'); + assert.strictEqual(LanguagesRegistry.instanceCount, 0, 'Error: Test run should not leak in LanguagesRegistry.'); } diff --git a/test/unit/electron/renderer.html b/test/unit/electron/renderer.html index 617a3bac13837..5fcbc9661aa11 100644 --- a/test/unit/electron/renderer.html +++ b/test/unit/electron/renderer.html @@ -23,16 +23,6 @@ window.alert = function () { throw new Error('window.alert() is not supported in tests!'); } window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); } - // Ignore uncaught cancelled promise errors - window.addEventListener('unhandledrejection', e => { - const name = e && e.reason && e.reason.name; - - if (name === 'Canceled') { - e.preventDefault(); - e.stopPropagation(); - } - }); - mocha.setup({ ui: 'tdd', timeout: typeof process.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] === 'string' ? 30000 : 5000, diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 21ef972f7c43b..cbdb4ec7adb57 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -166,12 +166,57 @@ function loadTestModules(opts) { }).then(loadModules); } +let currentTestTitle; + function loadTests(opts) { + //#region Unexpected Output + + const _allowedTestOutput = new Set([ + 'The vm module of Node.js is deprecated in the renderer process and will be removed.', + ]); + + const _allowedTestsWithOutput = new Set([ + 'creates a snapshot', // https://github.com/microsoft/vscode/issues/192439 + 'validates a snapshot', // https://github.com/microsoft/vscode/issues/192439 + 'cleans up old snapshots', // https://github.com/microsoft/vscode/issues/192439 + 'issue #149412: VS Code hangs when bad semantic token data is received', // https://github.com/microsoft/vscode/issues/192440 + 'issue #134973: invalid semantic tokens should be handled better', // https://github.com/microsoft/vscode/issues/192440 + 'issue #148651: VSCode UI process can hang if a semantic token with negative values is returned by language service', // https://github.com/microsoft/vscode/issues/192440 + 'issue #149130: vscode freezes because of Bracket Pair Colorization', // https://github.com/microsoft/vscode/issues/192440 + 'property limits', // https://github.com/microsoft/vscode/issues/192443 + 'Error events', // https://github.com/microsoft/vscode/issues/192443 + 'Ensure output channel is logged to', // https://github.com/microsoft/vscode/issues/192443 + 'guards calls after runs are ended' // https://github.com/microsoft/vscode/issues/192468 + ]); + + let _testsWithUnexpectedOutput = false; + + for (const consoleFn of [console.log, console.error, console.info, console.warn, console.trace, console.debug]) { + console[consoleFn.name] = function (msg) { + if (!_allowedTestOutput.has(msg) && !_allowedTestsWithOutput.has(currentTestTitle)) { + _testsWithUnexpectedOutput = true; + consoleFn.apply(console, arguments); + } + }; + } + + //#endregion + + //#region Unexpected / Loader Errors + const _unexpectedErrors = []; const _loaderErrors = []; - // collect loader errors + const _allowedTestsWithUnhandledRejections = new Set([ + // Lifecycle tests + 'onWillShutdown - join with error is handled', + 'onBeforeShutdown - veto with error is treated as veto', + 'onBeforeShutdown - final veto with error is treated as veto', + // Search tests + 'Search Model: Search reports timed telemetry on search when error is called' + ]); + loader.require.config({ onError(err) { _loaderErrors.push(err); @@ -179,8 +224,22 @@ function loadTests(opts) { } }); - // collect unexpected errors loader.require(['vs/base/common/errors'], function (errors) { + + process.on('uncaughtException', error => errors.onUnexpectedError(error)); + process.on('unhandledRejection', (reason, promise) => { + errors.onUnexpectedError(reason); + promise.catch(() => {}); + }); + window.addEventListener('unhandledrejection', event => { + event.preventDefault(); // Do not log to test output, we show an error later when test ends + event.stopPropagation(); + + if (!_allowedTestsWithUnhandledRejections.has(currentTestTitle)) { + errors.onUnexpectedError(event.reason); + } + }); + errors.setUnexpectedErrorHandler(function (err) { let stack = (err ? err.stack : null); if (!stack) { @@ -191,6 +250,8 @@ function loadTests(opts) { }); }); + //#endregion + return loadWorkbenchTestingUtilsModule().then((workbenchTestingModule) => { const assertCleanState = workbenchTestingModule.assertCleanState; @@ -200,24 +261,30 @@ function loadTests(opts) { }); }); - return loadTestModules(opts).then(() => { - suite('Unexpected Errors & Loader Errors', function () { - test('should not have unexpected errors', function () { - const errors = _unexpectedErrors.concat(_loaderErrors); - if (errors.length) { - errors.forEach(function (stack) { - console.error(''); - console.error(stack); - }); - assert.ok(false, errors); - } - }); - - test('assertCleanState - check that registries are clean and objects are disposed at the end of test running', () => { - assertCleanState(); - }); - }); + teardown(() => { + + // should not have unexpected output + if (_testsWithUnexpectedOutput) { + assert.ok(false, 'Error: Unexpected console output in test run. Please ensure no console.[log|error|info|warn] usage in tests or runtime errors.'); + } + + // should not have unexpected errors + const errors = _unexpectedErrors.concat(_loaderErrors); + if (errors.length) { + for (const error of errors) { + console.error(`Error: Test run should not have unexpected errors:\n${error}`); + } + assert.ok(false, 'Error: Test run should not have unexpected errors.'); + } }); + + suiteTeardown(() => { // intentionally not in teardown because some tests only cleanup in suiteTeardown + + // should have cleaned up in registries + assertCleanState(); + }); + + return loadTestModules(opts); }); } @@ -329,9 +396,10 @@ function runTests(opts) { }); }); + runner.on('test', test => currentTestTitle = test.title); + if (opts.dev) { runner.on('fail', (test, err) => { - console.error(test.fullTitle()); console.error(err.stack); });