Merge branch 'master' into joh/bulkEditPreview

This commit is contained in:
Johannes Rieken 2019-12-20 12:01:15 +01:00
commit ddb6d058f7
172 changed files with 4476 additions and 2623 deletions

View file

@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
---
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- Also please test using the latest insiders build to make sure your issue has not already been fixed: https://code.visualstudio.com/insiders/ -->

View file

@ -4,6 +4,7 @@ about: Suggest an idea for this project
---
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- Describe the feature you'd like. -->

View file

@ -131,5 +131,11 @@
action: 'updateLabels',
addLabel: 'a11ymas'
},
{
type: 'label',
name: '*off-topic',
action: 'close',
comment: "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
}
]
}

4
.github/copycat.yml vendored
View file

@ -1,5 +1,5 @@
{
perform: true,
perform: false,
target_owner: 'chrmarti',
target_repo: 'testissues'
}
}

112
.vscode/launch.json vendored
View file

@ -19,7 +19,10 @@
"restart": true,
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"hidden": true,
}
},
{
"type": "chrome",
@ -35,7 +38,10 @@
"port": 5876,
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"hidden": true,
}
},
{
"type": "node",
@ -53,7 +59,10 @@
"port": 5875,
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"hidden": true,
}
},
{
"type": "extensionHost",
@ -67,7 +76,11 @@
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"group": "5_tests",
"order": 6
}
},
{
"type": "extensionHost",
@ -82,7 +95,11 @@
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"group": "5_tests",
"order": 3
}
},
{
"type": "extensionHost",
@ -96,7 +113,11 @@
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"group": "5_tests",
"order": 4
}
},
{
"type": "extensionHost",
@ -110,13 +131,20 @@
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"group": "5_tests",
"order": 5
}
},
{
"type": "chrome",
"request": "attach",
"name": "Attach to VS Code",
"port": 9222
"port": 9222,
"presentation": {
"hidden": true
}
},
{
"type": "chrome",
@ -142,7 +170,10 @@
"--inspect=5875",
"--no-cached-data"
],
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}",
"presentation": {
"hidden": true
}
},
{
"type": "node",
@ -157,7 +188,11 @@
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
],
"presentation": {
"group": "2_launch",
"order": 1
}
},
{
"type": "node",
@ -167,13 +202,21 @@
"runtimeArgs": [
"web"
],
"presentation": {
"group": "2_launch",
"order": 2
}
},
{
"type": "chrome",
"request": "launch",
"name": "Launch VS Code (Web, Chrome)",
"url": "http://localhost:8080",
"preLaunchTask": "Run web"
"preLaunchTask": "Run web",
"presentation": {
"group": "2_launch",
"order": 3
}
},
{
"type": "node",
@ -184,7 +227,11 @@
"cwd": "${workspaceFolder}/extensions/git",
"outFiles": [
"${workspaceFolder}/extensions/git/out/**/*.js"
]
],
"presentation": {
"group": "5_tests",
"order": 10
}
},
{
"type": "extensionHost",
@ -198,7 +245,11 @@
],
"outFiles": [
"${workspaceFolder}/extensions/markdown-language-features/out/**/*.js"
]
],
"presentation": {
"group": "5_tests",
"order": 7
}
},
{
"type": "extensionHost",
@ -212,7 +263,11 @@
],
"outFiles": [
"${workspaceFolder}/extensions/typescript-language-features/out/**/*.js"
]
],
"presentation": {
"group": "5_tests",
"order": 8
}
},
{
"type": "node",
@ -236,6 +291,9 @@
],
"env": {
"MOCHA_COLORS": "true"
},
"presentation": {
"hidden": true
}
},
{
@ -276,28 +334,44 @@
"Launch VS Code",
"Attach to Main Process",
"Attach to Extension Host"
]
],
"presentation": {
"group": "1_vscode",
"order": 1
}
},
{
"name": "Search and Renderer processes",
"configurations": [
"Launch VS Code",
"Attach to Search Process"
]
],
"presentation": {
"group": "1_vscode",
"order": 4
}
},
{
"name": "Renderer and Extension Host processes",
"configurations": [
"Launch VS Code",
"Attach to Extension Host"
]
],
"presentation": {
"group": "1_vscode",
"order": 3
}
},
{
"name": "Debug Unit Tests",
"configurations": [
"Attach to VS Code",
"Run Unit Tests"
]
},
],
"presentation": {
"group": "1_vscode",
"order": 2
}
}
]
}

View file

@ -62,6 +62,10 @@
"name": "vs/workbench/contrib/emmet",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/experiments",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/extensions",
"project": "vscode-workbench"
@ -270,6 +274,10 @@
"name": "vs/workbench/services/remote",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/search",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/textfile",
"project": "vscode-workbench"

View file

@ -8,8 +8,15 @@ import * as fs from 'fs';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature } from 'vscode-languageclient';
import {
languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace,
Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList, SemanticTokensLegend,
SemanticTokensProvider, SemanticTokens
} from 'vscode';
import {
LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams,
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange
} from 'vscode-languageclient';
import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
import { activateTagClosing } from './tagClosing';
import TelemetryReporter from 'vscode-extension-telemetry';
@ -23,6 +30,18 @@ namespace MatchingTagPositionRequest {
export const type: RequestType<TextDocumentPositionParams, Position | null, any, any> = new RequestType('html/matchingTagPosition');
}
// experimental: semantic tokens
interface SemanticTokenParams {
textDocument: TextDocumentIdentifier;
ranges?: LspRange[];
}
namespace SemanticTokenRequest {
export const type: RequestType<SemanticTokenParams, number[] | null, any, any> = new RequestType('html/semanticTokens');
}
namespace SemanticTokenLegendRequest {
export const type: RequestType0<{ types: string[]; modifiers: string[] } | null, any, any> = new RequestType0('html/semanticTokenLegend');
}
interface IPackageInfo {
name: string;
version: string;
@ -132,6 +151,24 @@ export function activate(context: ExtensionContext) {
updateFormatterRegistration();
toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() });
toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration()));
client.sendRequest(SemanticTokenLegendRequest.type).then(legend => {
if (legend) {
const provider: SemanticTokensProvider = {
provideSemanticTokens(doc, opts) {
const params: SemanticTokenParams = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc),
ranges: opts.ranges?.map(r => client.code2ProtocolConverter.asRange(r))
};
return client.sendRequest(SemanticTokenRequest.type, params).then(data => {
return data && new SemanticTokens(new Uint32Array(data));
});
}
};
toDispose.push(languages.registerSemanticTokensProvider(documentSelector, provider, new SemanticTokensLegend(legend.types, legend.modifiers)));
}
});
});
function updateFormatterRegistration() {

View file

@ -6,11 +6,13 @@
import {
createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType,
DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities,
Position, ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification,
WorkspaceFolder, DocumentColorRequest, ColorInformation, ColorPresentationRequest, TextDocumentSyncKind
ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification,
DocumentColorRequest, ColorPresentationRequest, TextDocumentSyncKind
} from 'vscode-languageserver';
import { TextDocument, Diagnostic, DocumentLink, SymbolInformation } from 'vscode-html-languageservice';
import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes';
import {
getLanguageModes, LanguageModes, Settings, TextDocument, Position, Diagnostic, WorkspaceFolder, ColorInformation,
Range, DocumentLink, SymbolInformation, TextDocumentIdentifier
} from './modes/languageModes';
import { format } from './modes/formatting';
import { pushAll } from './utils/arrays';
@ -29,6 +31,18 @@ namespace MatchingTagPositionRequest {
export const type: RequestType<TextDocumentPositionParams, Position | null, any, any> = new RequestType('html/matchingTagPosition');
}
// experimental: semantic tokens
interface SemanticTokenParams {
textDocument: TextDocumentIdentifier;
ranges?: Range[];
}
namespace SemanticTokenRequest {
export const type: RequestType<SemanticTokenParams, number[] | null, any, any> = new RequestType('html/semanticTokens');
}
namespace SemanticTokenLegendRequest {
export const type: RequestType<void, { types: string[]; modifiers: string[] } | null, any, any> = new RequestType('html/semanticTokenLegend');
}
// Create a connection for the server
const connection: IConnection = createConnection();
@ -500,5 +514,45 @@ connection.onRequest(MatchingTagPositionRequest.type, (params, token) => {
}, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token);
});
connection.onRequest(MatchingTagPositionRequest.type, (params, token) => {
return runSafe(() => {
const document = documents.get(params.textDocument.uri);
if (document) {
const pos = params.position;
if (pos.character > 0) {
const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1));
if (mode && mode.findMatchingTagPosition) {
return mode.findMatchingTagPosition(document, pos);
}
}
}
return null;
}, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token);
});
connection.onRequest(SemanticTokenRequest.type, (params, token) => {
return runSafe(() => {
const document = documents.get(params.textDocument.uri);
if (document) {
const jsMode = languageModes.getMode('javascript');
if (jsMode && jsMode.getSemanticTokens) {
return jsMode.getSemanticTokens(document, params.ranges);
}
}
return null;
}, null, `Error while computing semantic tokens for ${params.textDocument.uri}`, token);
});
connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => {
return runSafe(() => {
const jsMode = languageModes.getMode('javascript');
if (jsMode && jsMode.getSemanticTokenLegend) {
return jsMode.getSemanticTokenLegend();
}
return null;
}, null, `Error while computing semantic tokens legend`, token);
});
// Listen on the connection
connection.listen();

View file

@ -18,10 +18,10 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
let cleanupInterval: NodeJS.Timer | undefined = undefined;
if (cleanupIntervalTimeInSec > 0) {
cleanupInterval = setInterval(() => {
let cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000;
let uris = Object.keys(languageModels);
for (let uri of uris) {
let languageModelInfo = languageModels[uri];
const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000;
const uris = Object.keys(languageModels);
for (const uri of uris) {
const languageModelInfo = languageModels[uri];
if (languageModelInfo.cTime < cutoffTime) {
delete languageModels[uri];
nModels--;
@ -32,14 +32,14 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
return {
get(document: TextDocument): T {
let version = document.version;
let languageId = document.languageId;
let languageModelInfo = languageModels[document.uri];
const version = document.version;
const languageId = document.languageId;
const languageModelInfo = languageModels[document.uri];
if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) {
languageModelInfo.cTime = Date.now();
return languageModelInfo.languageModel;
}
let languageModel = parse(document);
const languageModel = parse(document);
languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() };
if (!languageModelInfo) {
nModels++;
@ -48,8 +48,8 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
if (nModels === maxEntries) {
let oldestTime = Number.MAX_VALUE;
let oldestUri = null;
for (let uri in languageModels) {
let languageModelInfo = languageModels[uri];
for (const uri in languageModels) {
const languageModelInfo = languageModels[uri];
if (languageModelInfo.cTime < oldestTime) {
oldestUri = uri;
oldestTime = languageModelInfo.cTime;
@ -64,7 +64,7 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
},
onDocumentRemoved(document: TextDocument) {
let uri = document.uri;
const uri = document.uri;
if (languageModels[uri]) {
delete languageModels[uri];
nModels--;

View file

@ -4,11 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache';
import { TextDocument, Position, Range, CompletionList } from 'vscode-html-languageservice';
import { Stylesheet, FoldingRange, LanguageService as CSSLanguageService } from 'vscode-css-languageservice';
import { LanguageMode, Workspace } from './languageModes';
import { Stylesheet, LanguageService as CSSLanguageService } from 'vscode-css-languageservice';
import { FoldingRange, LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList } from './languageModes';
import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport';
import { Color } from 'vscode-languageserver';
export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache<HTMLDocumentRegions>, workspace: Workspace): LanguageMode {
let embeddedCSSDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css'));

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, Position, LanguageService, TokenType, Range } from 'vscode-html-languageservice';
import { TextDocument, Position, LanguageService, TokenType, Range } from './languageModes';
export interface LanguageRange extends Range {
languageId: string | undefined;

View file

@ -3,8 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, Range, TextEdit, FormattingOptions, Position } from 'vscode-html-languageservice';
import { LanguageModes, Settings, LanguageModeRange } from './languageModes';
import { LanguageModes, Settings, LanguageModeRange, TextDocument, Range, TextEdit, FormattingOptions, Position } from './languageModes';
import { pushAll } from '../utils/arrays';
import { isEOL } from '../utils/strings';

View file

@ -3,9 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, CancellationToken, Position, Range } from 'vscode-languageserver';
import { FoldingRange } from 'vscode-html-languageservice';
import { LanguageModes, LanguageMode } from './languageModes';
import { TextDocument, FoldingRange, Position, Range, LanguageModes, LanguageMode } from './languageModes';
import { CancellationToken } from 'vscode-languageserver';
export function getFoldingRanges(languageModes: LanguageModes, document: TextDocument, maxRanges: number | undefined, _cancellationToken: CancellationToken | null): FoldingRange[] {
let htmlMode = languageModes.getMode('html');

View file

@ -7,9 +7,9 @@ import { getLanguageModelCache } from '../languageModelCache';
import {
LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions,
HTMLFormatConfiguration, SelectionRange,
TextDocument, Position, Range, CompletionItem, FoldingRange
} from 'vscode-html-languageservice';
import { LanguageMode, Workspace } from './languageModes';
TextDocument, Position, Range, CompletionItem, FoldingRange,
LanguageMode, Workspace
} from './languageModes';
import { getPathCompletionParticipant } from './pathCompletion';
export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: Workspace): LanguageMode {

View file

@ -7,16 +7,18 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache
import {
SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation,
Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString,
DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange
} from 'vscode-html-languageservice';
import { LanguageMode, Settings } from './languageModes';
DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange,
LanguageMode, Settings
} from './languageModes';
import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings';
import { HTMLDocumentRegions } from './embeddedSupport';
import * as ts from 'typescript';
import { join } from 'path';
import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens';
const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents
const TS_FILE_NAME = 'vscode://javascript/2.ts';
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
let jquery_d_ts = join(__dirname, '../lib/jquery.d.ts'); // when packaged
@ -38,10 +40,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
}
const host: ts.LanguageServiceHost = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [FILE_NAME, jquery_d_ts],
getScriptKind: () => ts.ScriptKind.JS,
getScriptFileNames: () => [FILE_NAME, TS_FILE_NAME, jquery_d_ts],
getScriptKind: (fileName) => fileName === TS_FILE_NAME ? ts.ScriptKind.TS : ts.ScriptKind.JS,
getScriptVersion: (fileName: string) => {
if (fileName === FILE_NAME) {
if (fileName === FILE_NAME || fileName === TS_FILE_NAME) {
return String(scriptFileVersion);
}
return '1'; // default lib an jquery.d.ts are static
@ -49,7 +51,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
getScriptSnapshot: (fileName: string) => {
let text = '';
if (startsWith(fileName, 'vscode:')) {
if (fileName === FILE_NAME) {
if (fileName === FILE_NAME || fileName === TS_FILE_NAME) {
text = currentTextDocument.getText();
}
} else {
@ -314,6 +316,16 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
onDocumentRemoved(document: TextDocument) {
jsDocuments.onDocumentRemoved(document);
},
getSemanticTokens(document: TextDocument, ranges: Range[] | undefined): number[] {
updateCurrentTextDocument(document);
if (!ranges) {
ranges = [Range.create(Position.create(0, 0), document.positionAt(document.getText().length))];
}
return getSemanticTokens(jsLanguageService, currentTextDocument, TS_FILE_NAME, ranges);
},
getSemanticTokenLegend(): { types: string[], modifiers: string[] } {
return getSemanticTokenLegend();
},
dispose() {
jsLanguageService.dispose();
jsDocuments.dispose();
@ -321,6 +333,9 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
};
}
function convertRange(document: TextDocument, span: { start: number | undefined, length: number | undefined }): Range {
if (typeof span.start === 'undefined') {
const pos = document.positionAt(0);

View file

@ -0,0 +1,153 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, Range } from './languageModes';
import * as ts from 'typescript';
type SemanticTokenData = { offset: number, length: number, typeIdx: number, modifierSet: number };
export function getSemanticTokens(jsLanguageService: ts.LanguageService, currentTextDocument: TextDocument, fileName: string, ranges: Range[]) {
//https://ts-ast-viewer.com/#code/AQ0g2CmAuwGbALzAJwG4BQZQGNwEMBnQ4AQQEYBmYAb2C22zgEtJwATJVTRxgcwD27AQAp8AGmAAjAJS0A9POB8+7NQ168oscAJz5wANXwAnLug2bsJmAFcTAO2XAA1MHyvgu-UdOeWbOw8ViAAvpagocBAA
let resultTokens: SemanticTokenData[] = [];
// const tokens = jsLanguageService.getSemanticClassifications(fileName, { start: 0, length: currentTextDocument.getText().length });
// for (let token of tokens) {
// const typeIdx = tokenFromClassificationMapping[token.classificationType];
// if (typeIdx !== undefined) {
// resultTokens.push({ offset: token.textSpan.start, length: token.textSpan.length, typeIdx, modifierSet: 0 });
// }
// }
const program = jsLanguageService.getProgram();
if (program) {
const typeChecker = program.getTypeChecker();
function visit(node: ts.Node) {
if (node.kind === ts.SyntaxKind.Identifier) {
const symbol = typeChecker.getSymbolAtLocation(node);
if (symbol) {
const decl = symbol.valueDeclaration || symbol.declarations[0];
if (decl) {
let typeIdx = tokenFromDeclarationMapping[decl.kind];
let modifierSet = 0;
if (node.parent) {
const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind];
if (parentTypeIdx === typeIdx && (<ts.NamedDeclaration>node.parent).name === node) {
modifierSet = TokenModifier.declaration;
}
}
const modifiers = ts.getCombinedModifierFlags(decl);
if (modifiers & ts.ModifierFlags.Static) {
modifierSet |= TokenModifier.static;
}
if (modifiers & ts.ModifierFlags.Async) {
modifierSet |= TokenModifier.async;
}
if (typeIdx !== undefined) {
resultTokens.push({ offset: node.getStart(), length: node.getWidth(), typeIdx, modifierSet });
}
}
}
}
ts.forEachChild(node, visit);
}
const sourceFile = program.getSourceFile(fileName);
if (sourceFile) {
visit(sourceFile);
}
}
resultTokens = resultTokens.sort((d1, d2) => d1.offset - d2.offset);
const offsetRanges = ranges.map(r => ({ startOffset: currentTextDocument.offsetAt(r.start), endOffset: currentTextDocument.offsetAt(r.end) })).sort((d1, d2) => d1.startOffset - d2.startOffset);
let rangeIndex = 0;
let currRange = offsetRanges[rangeIndex++];
let prefLine = 0;
let prevChar = 0;
let encodedResult: number[] = [];
for (let k = 0; k < resultTokens.length && currRange; k++) {
const curr = resultTokens[k];
if (currRange.startOffset <= curr.offset && curr.offset + curr.length <= currRange.endOffset) {
// token inside a range
const startPos = currentTextDocument.positionAt(curr.offset);
if (prefLine !== startPos.line) {
prevChar = 0;
}
encodedResult.push(startPos.line - prefLine); // line delta
encodedResult.push(startPos.character - prevChar); // line delta
encodedResult.push(curr.length); // length
encodedResult.push(curr.typeIdx); // tokenType
encodedResult.push(curr.modifierSet); // tokenModifier
prefLine = startPos.line;
prevChar = startPos.character;
} else if (currRange.endOffset >= curr.offset) {
currRange = offsetRanges[rangeIndex++];
}
}
return encodedResult;
}
export function getSemanticTokenLegend() {
return { types: tokenTypes, modifiers: tokenModifiers };
}
const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'parameterType', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member'];
const tokenModifiers: string[] = ['declaration', 'static', 'async'];
enum TokenType {
'class' = 0,
'enum' = 1,
'interface' = 2,
'namespace' = 3,
'parameterType' = 4,
'type' = 5,
'parameter' = 6,
'variable' = 7,
'property' = 8,
'constant' = 9,
'function' = 10,
'member' = 11
}
enum TokenModifier {
'declaration' = 0x01,
'static' = 0x02,
'async' = 0x04,
}
// const tokenFromClassificationMapping: { [name: string]: TokenType } = {
// [ts.ClassificationTypeNames.className]: TokenType.class,
// [ts.ClassificationTypeNames.enumName]: TokenType.enum,
// [ts.ClassificationTypeNames.interfaceName]: TokenType.interface,
// [ts.ClassificationTypeNames.moduleName]: TokenType.namespace,
// [ts.ClassificationTypeNames.typeParameterName]: TokenType.parameterType,
// [ts.ClassificationTypeNames.typeAliasName]: TokenType.type,
// [ts.ClassificationTypeNames.parameterName]: TokenType.parameter
// };
const tokenFromDeclarationMapping: { [name: string]: TokenType } = {
[ts.SyntaxKind.VariableDeclaration]: TokenType.variable,
[ts.SyntaxKind.Parameter]: TokenType.parameter,
[ts.SyntaxKind.PropertyDeclaration]: TokenType.property,
[ts.SyntaxKind.ModuleDeclaration]: TokenType.namespace,
[ts.SyntaxKind.EnumDeclaration]: TokenType.enum,
[ts.SyntaxKind.EnumMember]: TokenType.property,
[ts.SyntaxKind.ClassDeclaration]: TokenType.class,
[ts.SyntaxKind.MethodDeclaration]: TokenType.member,
[ts.SyntaxKind.FunctionDeclaration]: TokenType.function,
[ts.SyntaxKind.MethodSignature]: TokenType.member,
[ts.SyntaxKind.GetAccessor]: TokenType.property,
[ts.SyntaxKind.PropertySignature]: TokenType.property,
};

View file

@ -17,7 +17,8 @@ import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport';
import { getHTMLMode } from './htmlMode';
import { getJavaScriptMode } from './javascriptMode';
export { ColorInformation, ColorPresentation, Color };
export * from 'vscode-html-languageservice';
export { WorkspaceFolder } from 'vscode-languageserver';
export interface Settings {
css?: any;
@ -51,6 +52,8 @@ export interface LanguageMode {
findMatchingTagPosition?: (document: TextDocument, position: Position) => Position | null;
getFoldingRanges?: (document: TextDocument) => FoldingRange[];
onDocumentRemoved(document: TextDocument): void;
getSemanticTokens?(document: TextDocument, ranges: Range[] | undefined): number[];
getSemanticTokenLegend?(): { types: string[], modifiers: string[] };
dispose(): void;
}

View file

@ -3,11 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkspaceFolder } from 'vscode-languageserver';
import * as path from 'path';
import * as fs from 'fs';
import { URI } from 'vscode-uri';
import { ICompletionParticipant, TextDocument, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-html-languageservice';
import { ICompletionParticipant, TextDocument, CompletionItemKind, CompletionItem, TextEdit, Range, Position, WorkspaceFolder } from './languageModes';
import { startsWith } from '../utils/strings';
import { contains } from '../utils/arrays';

View file

@ -3,8 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LanguageModes } from './languageModes';
import { TextDocument, Position, Range, SelectionRange } from 'vscode-html-languageservice';
import { LanguageModes, TextDocument, Position, Range, SelectionRange } from './languageModes';
export function getSelectionRanges(languageModes: LanguageModes, document: TextDocument, positions: Position[]) {
const htmlMode = languageModes.getMode('html');

View file

@ -6,10 +6,7 @@ import 'mocha';
import * as assert from 'assert';
import * as path from 'path';
import { URI } from 'vscode-uri';
import { getLanguageModes } from '../modes/languageModes';
import { WorkspaceFolder } from 'vscode-languageserver';
import { TextDocument, CompletionList, CompletionItemKind, ClientCapabilities } from 'vscode-html-languageservice';
import { getLanguageModes, WorkspaceFolder, TextDocument, CompletionList, CompletionItemKind, ClientCapabilities} from '../modes/languageModes';
export interface ItemDescription {
label: string;
documentation?: string;

View file

@ -5,31 +5,32 @@
import 'mocha';
import * as assert from 'assert';
import * as embeddedSupport from '../modes/embeddedSupport';
import { getLanguageService, TextDocument } from 'vscode-html-languageservice';
import { getLanguageService } from 'vscode-html-languageservice';
import { TextDocument } from '../modes/languageModes';
suite('HTML Embedded Support', () => {
var htmlLanguageService = getLanguageService();
const htmlLanguageService = getLanguageService();
function assertLanguageId(value: string, expectedLanguageId: string | undefined): void {
let offset = value.indexOf('|');
const offset = value.indexOf('|');
value = value.substr(0, offset) + value.substr(offset + 1);
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
const document = TextDocument.create('test://test/test.html', 'html', 0, value);
let position = document.positionAt(offset);
const position = document.positionAt(offset);
let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
let languageId = docRegions.getLanguageAtPosition(position);
const docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
const languageId = docRegions.getLanguageAtPosition(position);
assert.equal(languageId, expectedLanguageId);
}
function assertEmbeddedLanguageContent(value: string, languageId: string, expectedContent: string): void {
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
const document = TextDocument.create('test://test/test.html', 'html', 0, value);
let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
let content = docRegions.getEmbeddedDocument(languageId);
const docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
const content = docRegions.getEmbeddedDocument(languageId);
assert.equal(content.getText(), expectedContent);
}

View file

@ -5,9 +5,8 @@
import 'mocha';
import * as assert from 'assert';
import { TextDocument } from 'vscode-html-languageservice';
import { getFoldingRanges } from '../modes/htmlFolding';
import { getLanguageModes } from '../modes/languageModes';
import { TextDocument, getLanguageModes } from '../modes/languageModes';
import { ClientCapabilities } from 'vscode-css-languageservice';
interface ExpectedIndentRange {
@ -17,13 +16,13 @@ interface ExpectedIndentRange {
}
function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number): void {
let document = TextDocument.create('test://foo/bar.json', 'json', 1, lines.join('\n'));
let workspace = {
const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n'));
const workspace = {
settings: {},
folders: [{ name: 'foo', uri: 'test://foo' }]
};
let languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST);
let actual = getFoldingRanges(languageModes, document, nRanges, null);
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST);
const actual = getFoldingRanges(languageModes, document, nRanges, null);
let actualRanges = [];
for (let i = 0; i < actual.length; i++) {
@ -40,7 +39,7 @@ function r(startLine: number, endLine: number, kind?: string): ExpectedIndentRan
suite('HTML Folding', () => {
test('Embedded JavaScript', () => {
let input = [
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script>',
@ -54,7 +53,7 @@ suite('HTML Folding', () => {
});
test('Embedded JavaScript - multiple areas', () => {
let input = [
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
/* 2*/'<script>',
@ -75,7 +74,7 @@ suite('HTML Folding', () => {
});
test('Embedded JavaScript - incomplete', () => {
let input = [
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
/* 2*/'<script>',
@ -91,7 +90,7 @@ suite('HTML Folding', () => {
});
test('Embedded JavaScript - regions', () => {
let input = [
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
/* 2*/'<script>',
@ -108,7 +107,7 @@ suite('HTML Folding', () => {
});
test('Embedded CSS', () => {
let input = [
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
/* 2*/'<style>',
@ -124,7 +123,7 @@ suite('HTML Folding', () => {
});
test('Embedded CSS - multiple areas', () => {
let input = [
const input = [
/* 0*/'<html>',
/* 1*/'<head style="color:red">',
/* 2*/'<style>',
@ -145,7 +144,7 @@ suite('HTML Folding', () => {
});
test('Embedded CSS - regions', () => {
let input = [
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
/* 2*/'<style>',
@ -163,7 +162,7 @@ suite('HTML Folding', () => {
// test('Embedded JavaScript - multi line comment', () => {
// let input = [
// const input = [
// /* 0*/'<html>',
// /* 1*/'<head>',
// /* 2*/'<script>',
@ -178,7 +177,7 @@ suite('HTML Folding', () => {
// });
test('Test limit', () => {
let input = [
const input = [
/* 0*/'<div>',
/* 1*/' <span>',
/* 2*/' <b>',

View file

@ -7,8 +7,7 @@ import * as path from 'path';
import * as fs from 'fs';
import * as assert from 'assert';
import { getLanguageModes } from '../modes/languageModes';
import { TextDocument, Range, FormattingOptions, ClientCapabilities } from 'vscode-html-languageservice';
import { getLanguageModes, TextDocument, Range, FormattingOptions, ClientCapabilities } from '../modes/languageModes';
import { format } from '../modes/formatting';
@ -168,7 +167,7 @@ suite('HTML Embedded Formatting', () => {
}
};
var content = [
const content = [
'<html>',
'',
'<body>',
@ -179,7 +178,7 @@ suite('HTML Embedded Formatting', () => {
'</html>',
].join('\n');
var expected = [
const expected = [
'<html>',
'',
'<body>',

View file

@ -5,10 +5,7 @@
import 'mocha';
import * as assert from 'assert';
import { SelectionRange } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { getLanguageModes } from '../modes/languageModes';
import { ClientCapabilities } from 'vscode-html-languageservice';
import { getLanguageModes, ClientCapabilities, TextDocument, SelectionRange} from '../modes/languageModes';
import { getSelectionRanges } from '../modes/selectionRanges';
function assertRanges(content: string, expected: (number | string)[][]): void {

View file

@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import { TextDocument, getLanguageModes, ClientCapabilities, Range, Position } from '../modes/languageModes';
interface ExpectedToken {
startLine: number;
character: number;
length: number;
tokenClassifiction: string;
}
function assertTokens(lines: string[], expected: ExpectedToken[], range?: Range, message?: string): void {
const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n'));
const workspace = {
settings: {},
folders: [{ name: 'foo', uri: 'test://foo' }]
};
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST);
if (!range) {
range = Range.create(Position.create(0, 0), document.positionAt(document.getText().length));
}
const jsMode = languageModes.getMode('javascript')!;
const legend = jsMode.getSemanticTokenLegend!();
const actual = jsMode.getSemanticTokens!(document, [range]);
let actualRanges = [];
let lastLine = 0;
let lastCharacter = 0;
for (let i = 0; i < actual.length; i += 5) {
const lineDelta = actual[i], charDelta = actual[i + 1], len = actual[i + 2], typeIdx = actual[i + 3], modSet = actual[i + 4];
const line = lastLine + lineDelta;
const character = lineDelta === 0 ? lastCharacter + charDelta : charDelta;
const tokenClassifiction = [legend.types[typeIdx], ...legend.modifiers.filter((_, i) => modSet & 1 << i)].join('.');
actualRanges.push(t(line, character, len, tokenClassifiction));
lastLine = line;
lastCharacter = character;
}
assert.deepEqual(actualRanges, expected, message);
}
function t(startLine: number, character: number, length: number, tokenClassifiction: string): ExpectedToken {
return { startLine, character, length, tokenClassifiction };
}
suite('JavaScript Semantic Tokens', () => {
test('variables', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script>',
/*3*/' var x = 9, y1 = [x];',
/*4*/' try {',
/*5*/' for (const s of y1) { }',
/*6*/' } catch (e) {',
/*7*/' throw y1;',
/*8*/' }',
/*9*/'</script>',
/*10*/'</head>',
/*11*/'</html>',
];
assertTokens(input, [
t(3, 6, 1, 'variable.declaration'), t(3, 13, 2, 'variable.declaration'), t(3, 19, 1, 'variable'),
t(5, 15, 1, 'variable.declaration'), t(5, 20, 2, 'variable'),
t(6, 11, 1, 'variable.declaration'),
t(7, 10, 2, 'variable')
]);
});
test('function', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script>',
/*3*/' function foo(p1) {',
/*4*/' return foo(Math.abs(p1))',
/*5*/' }',
/*6*/' `/${window.location}`.split("/").forEach(s => foo(s));',
/*7*/'</script>',
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
t(3, 11, 3, 'function.declaration'), t(3, 15, 2, 'parameter.declaration'),
t(4, 11, 3, 'function'), t(4, 15, 4, 'variable'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter'),
t(6, 6, 6, 'variable'), t(6, 13, 8, 'property'), t(6, 24, 5, 'member'), t(6, 35, 7, 'member'), t(6, 43, 1, 'parameter.declaration'), t(6, 48, 3, 'function'), t(6, 52, 1, 'parameter')
]);
});
test('members', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script>',
/*3*/' class A {',
/*4*/' static x = 9;',
/*5*/' f = 9;',
/*6*/' async m() { return A.x + await this.m(); };',
/*7*/' get s() { return this.f; ',
/*8*/' static t() { return new A().f; };',
/*9*/' constructor() {}',
/*10*/' }',
/*11*/'</script>',
/*12*/'</head>',
/*13*/'</html>',
];
assertTokens(input, [
t(3, 8, 1, 'class.declaration'),
t(4, 11, 1, 'property.declaration.static'),
t(5, 4, 1, 'property.declaration'),
t(6, 10, 1, 'member.declaration.async'), t(6, 23, 1, 'class'), t(6, 25, 1, 'property.static'), t(6, 40, 1, 'member.async'),
t(7, 8, 1, 'property.declaration'), t(7, 26, 1, 'property'),
t(8, 11, 1, 'member.declaration.static'), t(8, 28, 1, 'class'), t(8, 32, 1, 'property'),
]);
});
});

View file

@ -3,10 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DocumentContext } from 'vscode-html-languageservice';
import { DocumentContext, WorkspaceFolder } from '../modes/languageModes';
import { endsWith, startsWith } from '../utils/strings';
import * as url from 'url';
import { WorkspaceFolder } from 'vscode-languageserver';
export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext {
function getRootFolder(): string | undefined {

View file

@ -14,7 +14,7 @@
"extensions": [ ".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi", ".pyi", ".ipy"],
"aliases": [ "Python", "py" ],
"filenames": [ "Snakefile" ],
"firstLine": "^#!\\s*/.*\\bpython[0-9.-]*\\b",
"firstLine": "^#!\\s*/?.*\\bpython[0-9.-]*\\b",
"configuration": "./language-configuration.json"
}],
"grammars": [{

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.42.0",
"distro": "e49812b31f1c71911d0557284874d07c7b790f13",
"distro": "ec0412876eac04d73be9bcc6919aecfaffe2ab64",
"author": {
"name": "Microsoft Corporation"
},
@ -72,6 +72,7 @@
"@types/mocha": "2.2.39",
"@types/node": "^12.11.7",
"@types/sinon": "^1.16.36",
"@types/vscode-windows-registry": "^1.0.0",
"@types/webpack": "^4.4.10",
"@types/windows-foreground-love": "^0.3.0",
"@types/windows-mutex": "^0.4.0",

View file

@ -1,9 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode-windows-registry' {
export type HKEY = "HKEY_CURRENT_USER" | "HKEY_LOCAL_MACHINE" | "HKEY_CLASSES_ROOT" | "HKEY_USERS" | "HKEY_CURRENT_CONFIG";
export function GetStringRegKey(hive: HKEY, path: string, name: string): string | undefined;
}

View file

@ -5,7 +5,7 @@
@font-face {
font-family: "codicon";
src: url("./codicon.ttf?072cd8445a025297c265f9d008123381") format("truetype");
src: url("./codicon.ttf?17db7f5e5f31fd546e62218bb0823c0c") format("truetype");
}
.codicon[class*='codicon-'] {

View file

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer';
@ -28,7 +29,7 @@ suite('QuickOpen', () => {
assert.equal(entry2, model.getEntries(true)[0]);
});
test('QuickOpenDataSource', () => {
test('QuickOpenDataSource', async () => {
const model = new QuickOpenModel();
const entry1 = new QuickOpenEntry();
@ -42,8 +43,7 @@ suite('QuickOpen', () => {
assert.equal(true, ds.hasChildren(null!, model));
assert.equal(false, ds.hasChildren(null!, entry1));
ds.getChildren(null!, model).then((children: any[]) => {
assert.equal(3, children.length);
});
const children = await ds.getChildren(null!, model);
assert.equal(3, children.length);
});
});
});

View file

@ -833,4 +833,4 @@ suite('Quick Open Scorer', () => {
assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false);
assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true);
});
});
});

View file

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
@ -28,4 +29,4 @@ suite('ProgressBar', () => {
bar.dispose();
});
});
});

View file

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ok } from 'vs/base/common/assert';

View file

@ -6,7 +6,6 @@
import * as assert from 'assert';
import * as collections from 'vs/base/common/collections';
suite('Collections', () => {
test('forEach', () => {

View file

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as extpath from 'vs/base/common/extpath';
import * as platform from 'vs/base/common/platform';
@ -28,7 +29,6 @@ suite('Paths', () => {
assert.equal(extpath.getRoot('http://www/'), 'http://www/');
assert.equal(extpath.getRoot('file:///foo'), 'file:///');
assert.equal(extpath.getRoot('file://foo'), '');
});
test('isUNC', () => {

View file

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { guessMimeTypes, registerTextMime, suggestFilename } from 'vs/base/common/mime';
import { URI } from 'vs/base/common/uri';

View file

@ -2,10 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as types from 'vs/base/common/types';
suite('Types', () => {
test('isFunction', () => {
assert(!types.isFunction(undefined));
assert(!types.isFunction(null));

View file

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';

View file

@ -592,30 +592,34 @@ class SemanticColoringProviderStyling {
public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number {
const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet);
let metadata: number | undefined;
if (entry) {
return entry.metadata;
}
const tokenType = this._legend.tokenTypes[tokenTypeIndex];
const tokenModifiers: string[] = [];
for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
if (tokenModifierSet & 1) {
tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]);
metadata = entry.metadata;
} else {
const tokenType = this._legend.tokenTypes[tokenTypeIndex];
const tokenModifiers: string[] = [];
for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
if (tokenModifierSet & 1) {
tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]);
}
tokenModifierSet = tokenModifierSet >> 1;
}
tokenModifierSet = tokenModifierSet >> 1;
}
let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers);
if (typeof metadata === 'undefined') {
metadata = Constants.NO_STYLING;
metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers);
if (typeof metadata === 'undefined') {
metadata = Constants.NO_STYLING;
}
this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata);
}
if (this._logService.getLevel() === LogLevel.Trace) {
this._logService.trace(`getTokenStyleMetadata(${tokenType}${tokenModifiers.length ? ', ' + tokenModifiers.join(' ') : ''}): foreground: ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`);
const type = this._legend.tokenTypes[tokenTypeIndex];
const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : '';
this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`);
}
this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata);
return metadata;
}
}
const enum SemanticColoringConstants {

View file

@ -29,11 +29,11 @@
.monaco-editor .codelens-decoration .codicon {
line-height: inherit;
font-size: inherit;
font-size: 110%;
vertical-align: inherit;
}
.monaco-editor .codelens-decoration > a:hover .codicon::before {
text-decoration: underline;
cursor: pointer;
}

View file

@ -32,6 +32,7 @@ import { InitializingRangeProvider, ID_INIT_PROVIDER } from 'vs/editor/contrib/f
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { onUnexpectedError } from 'vs/base/common/errors';
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
const CONTEXT_FOLDING_ENABLED = new RawContextKey<boolean>('foldingEnabled', false);
@ -421,8 +422,12 @@ export class FoldingController extends Disposable implements IEditorContribution
if (region && region.startLineNumber === lineNumber) {
let isCollapsed = region.isCollapsed;
if (iconClicked || isCollapsed) {
let toToggle = [region];
if (e.event.middleButton || e.event.shiftKey) {
let toToggle = [];
let considerRegionsInside = this.shouldConsiderRegionsInside(e.event);
if (isCollapsed || (!isCollapsed && !considerRegionsInside)) {
toToggle.push(region);
}
if (considerRegionsInside) {
toToggle.push(...foldingModel.getRegionsInside(region, (r: FoldingRegion) => r.isCollapsed === isCollapsed));
}
foldingModel.toggleCollapseState(toToggle);
@ -433,6 +438,10 @@ export class FoldingController extends Disposable implements IEditorContribution
}).then(undefined, onUnexpectedError);
}
private shouldConsiderRegionsInside(event: IMouseEvent): boolean {
return event.middleButton || event.shiftKey;
}
public reveal(position: IPosition): void {
this.editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth);
}

View file

@ -13,6 +13,8 @@ export class FoldingDecorationProvider implements IDecorationProvider {
private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
afterContentClassName: 'inline-folded',
className: 'folded-background',
isWholeLine: true,
linesDecorationsClassName: 'codicon codicon-chevron-right'
});

View file

@ -6,6 +6,9 @@
import { ITextModel, IModelDecorationOptions, IModelDeltaDecoration, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
import { Event, Emitter } from 'vs/base/common/event';
import { FoldingRegions, ILineRange, FoldingRegion } from './foldingRanges';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { registerColor, editorSelectionBackground, darken, lighten } from 'vs/platform/theme/common/colorRegistry';
import * as nls from 'vs/nls';
export interface IDecorationProvider {
getDecorationOption(isCollapsed: boolean): IModelDecorationOptions;
@ -90,7 +93,6 @@ export class FoldingModel {
};
newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed) });
};
let i = 0;
let nextCollapsed = () => {
while (i < this._regions.length) {
@ -355,3 +357,12 @@ export function setCollapseStateForType(foldingModel: FoldingModel, type: string
}
foldingModel.toggleCollapseState(toToggle);
}
export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: lighten(editorSelectionBackground, 0.5), dark: darken(editorSelectionBackground, 0.5), hc: null }, nls.localize('editorSelectionBackground', "Color of the editor selection."));
registerThemingParticipant((theme, collector) => {
const foldBackground = theme.getColor(foldBackgroundBackground);
if (foldBackground) {
collector.addRule(`.monaco-editor .folded-background { background-color: ${foldBackground}; }`);
}
});

View file

@ -44,16 +44,16 @@ suite('BackupMainService', () => {
this.workspacesJsonPath = backupWorkspacesPath;
}
public toBackupPath(arg: URI | string): string {
toBackupPath(arg: URI | string): string {
const id = arg instanceof URI ? super.getFolderHash(arg) : arg;
return path.join(this.backupHome, id);
}
public getFolderHash(folderUri: URI): string {
getFolderHash(folderUri: URI): string {
return super.getFolderHash(folderUri);
}
public toLegacyBackupPath(folderPath: string): string {
toLegacyBackupPath(folderPath: string): string {
return path.join(this.backupHome, super.getLegacyFolderHash(folderPath));
}
}
@ -119,17 +119,16 @@ suite('BackupMainService', () => {
let service: TestBackupMainService;
let configService: TestConfigurationService;
setup(() => {
setup(async () => {
// Delete any existing backups completely and then re-create it.
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE).then(() => {
return pfs.mkdirp(backupHome);
}).then(() => {
configService = new TestConfigurationService();
service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService);
await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
await pfs.mkdirp(backupHome);
return service.initialize();
});
configService = new TestConfigurationService();
service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService);
return service.initialize();
});
teardown(() => {
@ -591,71 +590,71 @@ suite('BackupMainService', () => {
});
});
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', () => {
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', async () => {
service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase()));
assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]);
});
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]);
});
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', () => {
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', async () => {
const upperFooPath = fooFile.fsPath.toUpperCase();
service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath));
assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]);
});
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]);
});
suite('removeBackupPathSync', () => {
test('should remove folder workspaces from workspaces.json (folder workspace)', () => {
test('should remove folder workspaces from workspaces.json (folder workspace)', async () => {
service.registerFolderBackupSync(fooFile);
service.registerFolderBackupSync(barFile);
service.unregisterFolderBackupSync(fooFile);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]);
service.unregisterFolderBackupSync(barFile);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.folderURIWorkspaces, []);
});
});
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]);
service.unregisterFolderBackupSync(barFile);
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json2.folderURIWorkspaces, []);
});
test('should remove folder workspaces from workspaces.json (root workspace)', () => {
test('should remove folder workspaces from workspaces.json (root workspace)', async () => {
const ws1 = toWorkspaceBackupInfo(fooFile.fsPath);
service.registerWorkspaceBackupSync(ws1);
const ws2 = toWorkspaceBackupInfo(barFile.fsPath);
service.registerWorkspaceBackupSync(ws2);
service.unregisterWorkspaceBackupSync(ws1.workspace);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]);
service.unregisterWorkspaceBackupSync(ws2.workspace);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.rootURIWorkspaces, []);
});
});
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]);
service.unregisterWorkspaceBackupSync(ws2.workspace);
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json2.rootURIWorkspaces, []);
});
test('should remove empty workspaces from workspaces.json', () => {
test('should remove empty workspaces from workspaces.json', async () => {
service.registerEmptyWindowBackupSync('foo');
service.registerEmptyWindowBackupSync('bar');
service.unregisterEmptyWindowBackupSync('foo');
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.emptyWorkspaces, ['bar']);
service.unregisterEmptyWindowBackupSync('bar');
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.emptyWorkspaces, []);
});
});
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.emptyWorkspaces, ['bar']);
service.unregisterEmptyWindowBackupSync('bar');
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json2.emptyWorkspaces, []);
});
test('should fail gracefully when removing a path that doesn\'t exist', async () => {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError } from 'vs/platform/files/common/files';
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError, IFileSystemProviderCapabilitiesChangeEvent } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources';
@ -33,11 +33,14 @@ export class FileService extends Disposable implements IFileService {
//#region File System Provider
private _onDidChangeFileSystemProviderRegistrations: Emitter<IFileSystemProviderRegistrationEvent> = this._register(new Emitter<IFileSystemProviderRegistrationEvent>());
readonly onDidChangeFileSystemProviderRegistrations: Event<IFileSystemProviderRegistrationEvent> = this._onDidChangeFileSystemProviderRegistrations.event;
private _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter<IFileSystemProviderRegistrationEvent>());
readonly onDidChangeFileSystemProviderRegistrations = this._onDidChangeFileSystemProviderRegistrations.event;
private _onWillActivateFileSystemProvider: Emitter<IFileSystemProviderActivationEvent> = this._register(new Emitter<IFileSystemProviderActivationEvent>());
readonly onWillActivateFileSystemProvider: Event<IFileSystemProviderActivationEvent> = this._onWillActivateFileSystemProvider.event;
private _onWillActivateFileSystemProvider = this._register(new Emitter<IFileSystemProviderActivationEvent>());
readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event;
private _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter<IFileSystemProviderCapabilitiesChangeEvent>());
readonly onDidChangeFileSystemProviderCapabilities = this._onDidChangeFileSystemProviderCapabilities.event;
private readonly provider = new Map<string, IFileSystemProvider>();
@ -53,6 +56,7 @@ export class FileService extends Disposable implements IFileService {
// Forward events from provider
const providerDisposables = new DisposableStore();
providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes))));
providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider, scheme })));
if (typeof provider.onDidErrorOccur === 'function') {
providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error))));
}

View file

@ -28,6 +28,11 @@ export interface IFileService {
*/
readonly onDidChangeFileSystemProviderRegistrations: Event<IFileSystemProviderRegistrationEvent>;
/**
* An even that is fired when a registered file system provider changes it's capabilities.
*/
readonly onDidChangeFileSystemProviderCapabilities: Event<IFileSystemProviderCapabilitiesChangeEvent>;
/**
* An event that is fired when a file system provider is about to be activated. Listeners
* can join this event with a long running promise to help in the activation process.
@ -409,6 +414,11 @@ export interface IFileSystemProviderRegistrationEvent {
provider?: IFileSystemProvider;
}
export interface IFileSystemProviderCapabilitiesChangeEvent {
provider: IFileSystemProvider;
scheme: string;
}
export interface IFileSystemProviderActivationEvent {
scheme: string;
join(promise: Promise<void>): void;

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import { FileService } from 'vs/platform/files/common/fileService';
import { URI } from 'vs/base/common/uri';
import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities, IFileSystemProviderCapabilitiesChangeEvent } from 'vs/platform/files/common/files';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { NullLogService } from 'vs/platform/log/common/log';
import { timeout } from 'vs/base/common/async';
@ -17,6 +17,7 @@ suite('File Service', () => {
test('provider registration', async () => {
const service = new FileService(new NullLogService());
const resource = URI.parse('test://foo/bar');
const provider = new NullFileSystemProvider();
assert.equal(service.canHandleResource(resource), false);
@ -25,6 +26,11 @@ suite('File Service', () => {
registrations.push(e);
});
const capabilityChanges: IFileSystemProviderCapabilitiesChangeEvent[] = [];
service.onDidChangeFileSystemProviderCapabilities(e => {
capabilityChanges.push(e);
});
let registrationDisposable: IDisposable | undefined = undefined;
let callCount = 0;
service.onWillActivateFileSystemProvider(e => {
@ -32,7 +38,7 @@ suite('File Service', () => {
if (e.scheme === 'test' && callCount === 1) {
e.join(new Promise(resolve => {
registrationDisposable = service.registerProvider('test', new NullFileSystemProvider());
registrationDisposable = service.registerProvider('test', provider);
resolve();
}));
@ -48,6 +54,13 @@ suite('File Service', () => {
assert.equal(registrations[0].added, true);
assert.ok(registrationDisposable);
assert.equal(capabilityChanges.length, 0);
provider.setCapabilities(FileSystemProviderCapabilities.FileFolderCopy);
assert.equal(capabilityChanges.length, 1);
provider.setCapabilities(FileSystemProviderCapabilities.Readonly);
assert.equal(capabilityChanges.length, 2);
await service.activateProvider('test');
assert.equal(callCount, 2); // activation is called again
@ -109,4 +122,4 @@ suite('File Service', () => {
watcher3Disposable2.dispose();
assert.equal(disposeCounter, 2);
});
});
});

View file

@ -6,14 +6,22 @@
import { URI } from 'vs/base/common/uri';
import { FileSystemProviderCapabilities, IFileSystemProvider, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileChange } from 'vs/platform/files/common/files';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
export class NullFileSystemProvider implements IFileSystemProvider {
capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly;
onDidChangeCapabilities: Event<void> = Event.None;
onDidChangeFile: Event<readonly IFileChange[]> = Event.None;
private readonly _onDidChangeCapabilities = new Emitter<void>();
readonly onDidChangeCapabilities: Event<void> = this._onDidChangeCapabilities.event;
setCapabilities(capabilities: FileSystemProviderCapabilities): void {
this.capabilities = capabilities;
this._onDidChangeCapabilities.fire();
}
readonly onDidChangeFile: Event<readonly IFileChange[]> = Event.None;
constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { }

View file

@ -18,7 +18,7 @@ export interface ResolvedOptions {
}
export interface TunnelInformation {
detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[];
environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[];
}
export interface ResolverResult {

View file

@ -19,9 +19,9 @@ export interface RemoteTunnel {
}
export interface TunnelOptions {
remote: { port: number, host: string };
remoteAddress: { port: number, host: string };
localPort?: number;
name?: string;
label?: string;
}
export interface ITunnelProvider {
@ -33,10 +33,10 @@ export interface ITunnelService {
readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<number>;
readonly onTunnelClosed: Event<{ host: string, port: number }>;
openTunnel(remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remotePort: number): Promise<void>;
openTunnel(remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
}

View file

@ -13,12 +13,12 @@ export class NoOpTunnelService implements ITunnelService {
public readonly tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event;
openTunnel(_remotePort: number): Promise<RemoteTunnel> | undefined {
private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
openTunnel(_remoteHost: string, _remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined;
}
async closeTunnel(_remotePort: number): Promise<void> {
async closeTunnel(_remoteHost: string, _remotePort: number): Promise<void> {
}
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
throw new Error('Method not implemented.');

View file

@ -89,11 +89,11 @@ export class StorageMainService extends Disposable implements IStorageMainServic
private static readonly STORAGE_NAME = 'state.vscdb';
private readonly _onDidChangeStorage: Emitter<IStorageChangeEvent> = this._register(new Emitter<IStorageChangeEvent>());
readonly onDidChangeStorage: Event<IStorageChangeEvent> = this._onDidChangeStorage.event;
private readonly _onDidChangeStorage = this._register(new Emitter<IStorageChangeEvent>());
readonly onDidChangeStorage = this._onDidChangeStorage.event;
private readonly _onWillSaveState: Emitter<void> = this._register(new Emitter<void>());
readonly onWillSaveState: Event<void> = this._onWillSaveState.event;
private readonly _onWillSaveState = this._register(new Emitter<void>());
readonly onWillSaveState = this._onWillSaveState.event;
get items(): Map<string, string> { return this.storage.items; }

View file

@ -28,11 +28,11 @@ suite('StorageService', () => {
function removeData(scope: StorageScope): void {
const storage = new InMemoryStorageService();
storage.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar', scope);
strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!));
storage.store('test.remove', 'foobar', scope);
strictEqual('foobar', storage.get('test.remove', scope, (undefined)!));
storage.remove('Monaco.IDE.Core.Storage.Test.remove', scope);
ok(!storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!));
storage.remove('test.remove', scope);
ok(!storage.get('test.remove', scope, (undefined)!));
}
test('Get Data, Integer, Boolean (global, in-memory)', () => {
@ -46,34 +46,34 @@ suite('StorageService', () => {
function storeData(scope: StorageScope): void {
const storage = new InMemoryStorageService();
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, 'foobar'), 'foobar');
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, ''), '');
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 5), 5);
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 0), 0);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, true), true);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, false), false);
strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar');
strictEqual(storage.get('test.get', scope, ''), '');
strictEqual(storage.getNumber('test.getNumber', scope, 5), 5);
strictEqual(storage.getNumber('test.getNumber', scope, 0), 0);
strictEqual(storage.getBoolean('test.getBoolean', scope, true), true);
strictEqual(storage.getBoolean('test.getBoolean', scope, false), false);
storage.store('Monaco.IDE.Core.Storage.Test.get', 'foobar', scope);
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), 'foobar');
storage.store('test.get', 'foobar', scope);
strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar');
storage.store('Monaco.IDE.Core.Storage.Test.get', '', scope);
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), '');
storage.store('test.get', '', scope);
strictEqual(storage.get('test.get', scope, (undefined)!), '');
storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 5, scope);
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 5);
storage.store('test.getNumber', 5, scope);
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5);
storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 0, scope);
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 0);
storage.store('test.getNumber', 0, scope);
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0);
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', true, scope);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), true);
storage.store('test.getBoolean', true, scope);
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true);
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', false, scope);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), false);
storage.store('test.getBoolean', false, scope);
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false);
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getDefault', scope, 'getDefault'), 'getDefault');
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumberDefault', scope, 5), 5);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBooleanDefault', scope, true), true);
strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault');
strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5);
strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true);
}
function uniqueStorageDir(): string {

View file

@ -381,12 +381,13 @@ function registerDefaultClassifications(): void {
registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type');
registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]);
registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function'], ['support.function']]);
registerTokenType('macro', nls.localize('macro', "Style for macros."), undefined, 'function');
registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]);
registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable');
registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), undefined, 'variable');
registerTokenType('property', nls.localize('propertie', "Style for properties."), undefined, 'variable');
registerTokenType('property', nls.localize('property', "Style for properties."), undefined, 'variable');
registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined);
@ -394,7 +395,7 @@ function registerDefaultClassifications(): void {
tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined);
tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined);
tokenClassificationRegistry.registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined);
//tokenClassificationRegistry.registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined);
tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined);
tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined);
tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined);

View file

@ -47,7 +47,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (this.userDataSyncStoreService.userDataSyncStore) {
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
this._register(authTokenService.onDidChangeStatus(() => this.onDidChangeAuthTokenStatus()));
}
this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal));
@ -118,11 +117,4 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
return null;
}
private onDidChangeAuthTokenStatus(): void {
if (this.authTokenService.status === AuthTokenStatus.SignedOut) {
this.stop();
}
}
}

View file

@ -13,14 +13,14 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { IStateService } from 'vs/platform/state/node/state';
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display } from 'electron';
import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display, app } from 'electron';
import { parseLineAndColumnAware } from 'vs/code/node/paths';
import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows';
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
import product from 'vs/platform/product/common/product';
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
@ -160,14 +160,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private readonly windowsState: IWindowsState;
private lastClosedWindowState?: IWindowState;
private shuttingDown = false;
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
readonly onWindowReady = this._onWindowReady.event;
private readonly _onWindowClose = this._register(new Emitter<number>());
readonly onWindowClose: CommonEvent<number> = this._onWindowClose.event;
readonly onWindowClose = this._onWindowClose.event;
private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
readonly onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
readonly onWindowsCountChanged = this._onWindowsCountChanged.event;
constructor(
private readonly machineId: string,
@ -236,6 +238,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange());
}
// When a window looses focus, save all windows state. This allows to
// prevent loss of window-state data when OS is restarted without properly
// shutting down the application (https://github.com/microsoft/vscode/issues/87171)
app.on('browser-window-blur', () => {
if (!this.shuttingDown) {
this.saveWindowsState();
}
});
// Handle various lifecycle events around windows
this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown());
@ -292,6 +303,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
//
private onBeforeShutdown(): void {
this.shuttingDown = true;
this.saveWindowsState();
}
private saveWindowsState(): void {
const currentWindowsState: IWindowsState = {
openedWindows: [],
lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
@ -327,8 +344,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Persist
const state = getWindowsStateStoreData(currentWindowsState);
this.logService.trace('onBeforeShutdown', state);
this.stateService.setItem(WindowsMainService.windowsStateStorageKey, state);
if (this.shuttingDown) {
this.logService.trace('onBeforeShutdown', state);
}
}
// See note on #onBeforeShutdown() for details how these events are flowing
@ -999,10 +1019,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
restoreWindows = 'all'; // always reopen all windows when an update was applied
} else {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
restoreWindows = windowConfig?.restoreWindows || 'one';
restoreWindows = windowConfig?.restoreWindows || 'all'; // by default restore all windows
if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) {
restoreWindows = 'one';
restoreWindows = 'all'; // by default restore all windows
}
}

View file

@ -34,15 +34,18 @@ declare module 'vscode' {
}
export interface TunnelOptions {
remote: { port: number, host: string };
localPort?: number;
name?: string;
remoteAddress: { port: number, host: string };
// The desired local port. If this port can't be used, then another will be chosen.
localAddressPort?: number;
label?: string;
}
export interface Tunnel {
remote: { port: number, host: string };
remoteAddress: { port: number, host: string };
//The complete local address(ex. localhost:1234)
localAddress: string;
onDispose: Event<void>;
// Implementers of Tunnel should fire onDidDispose when dispose is called.
onDidDispose: Event<void>;
dispose(): void;
}
@ -52,10 +55,10 @@ declare module 'vscode' {
export interface TunnelInformation {
/**
* Tunnels that are detected by the extension. The remotePort is used for display purposes.
* The localAddress should be the complete local address(ex. localhost:1234) for connecting to the port. Tunnels provided through
* The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through
* detected are read-only from the forwarded ports UI.
*/
detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[];
environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[];
}
export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation;
@ -74,15 +77,16 @@ declare module 'vscode' {
* When not implemented, the core will use its default forwarding logic.
* When implemented, the core will use this to forward ports.
*/
forwardPort?(tunnelOptions: TunnelOptions): Thenable<Tunnel> | undefined;
tunnelFactory?: (tunnelOptions: TunnelOptions) => Thenable<Tunnel> | undefined;
}
export namespace workspace {
/**
* Forwards a port. Currently only works for a remote host of localhost.
* @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen.
* Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel.
* By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips.
* @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen.
*/
export function makeTunnel(forward: TunnelOptions): Thenable<Tunnel>;
export function openTunnel(tunnelOptions: TunnelOptions): Thenable<Tunnel>;
}
export interface ResourceLabelFormatter {
@ -1470,4 +1474,40 @@ declare module 'vscode' {
}
//#endregion
//#region color theme access
/**
* Represents a color theme kind.
*/
export enum ColorThemeKind {
Light = 1,
Dark = 2,
HighContrast = 3
}
/**
* Represents a color theme.
*/
export interface ColorTheme {
/**
* The kind of this color theme: light, dark or high contrast.
*/
readonly kind: ColorThemeKind;
}
export namespace window {
/**
* The currently active color theme as configured in the settings. The active
* theme can be changed via the `workbench.colorTheme` setting.
*/
export let activeColorTheme: ColorTheme;
/**
* An [event](#Event) which fires when the active theme changes or one of it's colors chnage.
*/
export const onDidChangeActiveColorTheme: Event<ColorTheme>;
}
}

View file

@ -48,6 +48,7 @@ import './mainThreadStatusBar';
import './mainThreadStorage';
import './mainThreadTelemetry';
import './mainThreadTerminalService';
import './mainThreadTheming';
import './mainThreadTreeViews';
import './mainThreadDownloadService';
import './mainThreadUrls';

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MainContext, IExtHostContext, ExtHostThemingShape, ExtHostContext, MainThreadThemingShape } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@extHostNamedCustomer(MainContext.MainThreadTheming)
export class MainThreadTheming implements MainThreadThemingShape {
private readonly _themeService: IThemeService;
private readonly _proxy: ExtHostThemingShape;
private readonly _themeChangeListener: IDisposable;
constructor(
extHostContext: IExtHostContext,
@IThemeService themeService: IThemeService
) {
this._themeService = themeService;
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTheming);
this._themeChangeListener = this._themeService.onThemeChange(e => {
this._proxy.$onColorThemeChange(this._themeService.getTheme().type);
});
}
dispose(): void {
this._themeChangeListener.dispose();
}
}

View file

@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService } from 'vs/platform/remote/common/tunnel';
import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel';
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
@ -22,15 +22,15 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
}
async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> {
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name);
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localPort, tunnelOptions.label);
if (tunnel) {
return TunnelDto.fromServiceTunnel(tunnel);
}
return undefined;
}
async $closeTunnel(remotePort: number): Promise<void> {
return this.remoteExplorerService.close(remotePort);
async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
return this.remoteExplorerService.close(remote);
}
async $registerCandidateFinder(): Promise<void> {
@ -44,11 +44,11 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
if (forward) {
return forward.then(tunnel => {
return {
tunnelRemotePort: tunnel.remote.port,
tunnelRemoteHost: tunnel.remote.host,
tunnelRemotePort: tunnel.remoteAddress.port,
tunnelRemoteHost: tunnel.remoteAddress.host,
localAddress: tunnel.localAddress,
dispose: () => {
this._proxy.$closeTunnel({ host: tunnel.remote.host, port: tunnel.remote.port });
this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port });
}
};
});

View file

@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
if (!viewContainer) {
viewContainer = this.viewContainersRegistry.registerViewContainer(id, ViewContainerLocation.Sidebar, true, extensionId);
viewContainer = this.viewContainersRegistry.registerViewContainer({ id, hideIfEmpty: true, name: title, extensionId }, ViewContainerLocation.Sidebar);
class CustomViewPaneContainer extends ViewPaneContainer {
constructor(
@ -327,7 +327,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
) {
super(id, `${id}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
}
}

View file

@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
export interface IExtensionApiFactory {
@ -125,7 +126,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments));
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol));
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
// Check that no named customers are missing
const expected: ProxyIdentifier<any>[] = values(ExtHostContext);
@ -548,6 +550,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
createInputBox(): vscode.InputBox {
return extHostQuickOpen.createInputBox(extension.identifier);
},
get activeColorTheme(): vscode.ColorTheme {
checkProposedApiEnabled(extension);
return extHostTheming.activeColorTheme;
},
onDidChangeActiveColorTheme(listener, thisArg?, disposables?) {
checkProposedApiEnabled(extension);
return extHostTheming.onDidChangeActiveColorTheme(listener, thisArg, disposables);
}
};
@ -666,7 +676,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration {
scope = arguments.length === 1 ? undefined : scope;
return configProvider.getConfiguration(section, scope, extension.identifier);
return configProvider.getConfiguration(section, scope, extension);
},
registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) {
return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider);
@ -714,9 +724,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => {
return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables);
},
makeTunnel: (forward: vscode.TunnelOptions) => {
openTunnel: (forward: vscode.TunnelOptions) => {
checkProposedApiEnabled(extension);
return extHostTunnelService.makeTunnel(forward);
return extHostTunnelService.openTunnel(forward);
}
};
@ -935,7 +945,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
DebugConsoleMode: extHostTypes.DebugConsoleMode,
Decoration: extHostTypes.Decoration,
WebviewContentState: extHostTypes.WebviewContentState,
UIKind: UIKind
UIKind: UIKind,
ColorThemeKind: extHostTypes.ColorThemeKind
};
};
}

View file

@ -47,7 +47,8 @@ import { createExtHostContextProxyIdentifier as createExtId, createMainContextPr
import * as search from 'vs/workbench/services/search/common/search';
import { SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@ -774,7 +775,7 @@ export interface MainThreadWindowShape extends IDisposable {
export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>;
$closeTunnel(remotePort: number): Promise<void>;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
$registerCandidateFinder(): Promise<void>;
$setTunnelProvider(): Promise<void>;
}
@ -1393,9 +1394,15 @@ export interface ExtHostStorageShape {
$acceptValue(shared: boolean, key: string, value: object | undefined): void;
}
export interface ExtHostThemingShape {
$onColorThemeChange(themeType: string): void;
}
export interface MainThreadThemingShape extends IDisposable {
}
export interface ExtHostTunnelServiceShape {
$findCandidatePorts(): Promise<{ port: number, detail: string }[]>;
$findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>;
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
}
@ -1441,6 +1448,7 @@ export const MainContext = {
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService')
};
@ -1475,6 +1483,7 @@ export const ExtHostContext = {
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
ExtHostLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
ExtHostTheming: createMainId<ExtHostThemingShape>('ExtHostTheming'),
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService')
};

View file

@ -13,13 +13,14 @@ import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigu
import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { isObject } from 'vs/base/common/types';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Barrier } from 'vs/base/common/async';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ILogService } from 'vs/platform/log/common/log';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
function lookUp(tree: any, key: string) {
if (key) {
@ -149,14 +150,17 @@ export class ExtHostConfigProvider {
this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous));
}
getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration {
getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionDescription?: IExtensionDescription): vscode.WorkspaceConfiguration {
const overrides = scopeToOverrides(scope) || {};
if (overrides.overrideIdentifier && extensionDescription) {
checkProposedApiEnabled(extensionDescription);
}
const config = this._toReadonlyValue(section
? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section)
: this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace));
if (section) {
this._validateConfigurationAccess(section, overrides, extensionId);
this._validateConfigurationAccess(section, overrides, extensionDescription?.identifier);
}
function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null {
@ -179,7 +183,7 @@ export class ExtHostConfigProvider {
return typeof lookUp(config, key) !== 'undefined';
},
get: <T>(key: string, defaultValue?: T) => {
this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionId);
this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionDescription?.identifier);
let result = lookUp(config, key);
if (typeof result === 'undefined') {
result = defaultValue;

View file

@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
value: {
authority,
options,
tunnelInformation: { detectedTunnels: result.detectedTunnels }
tunnelInformation: { environmentTunnels: result.environmentTunnels }
}
};
} catch (err) {

View file

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ColorTheme, ColorThemeKind } from './extHostTypes';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostThemingShape } from 'vs/workbench/api/common/extHost.protocol';
import { Emitter, Event } from 'vs/base/common/event';
export class ExtHostTheming implements ExtHostThemingShape {
readonly _serviceBrand: undefined;
private _actual: ColorTheme;
private _onDidChangeActiveColorTheme: Emitter<ColorTheme>;
constructor(
@IExtHostRpcService _extHostRpc: IExtHostRpcService
) {
this._actual = new ColorTheme(ColorThemeKind.Dark);
this._onDidChangeActiveColorTheme = new Emitter<ColorTheme>();
}
public get activeColorTheme(): ColorTheme {
return this._actual;
}
$onColorThemeChange(type: string): void {
let kind = type === 'light' ? ColorThemeKind.Light : type === 'dark' ? ColorThemeKind.Dark : ColorThemeKind.HighContrast;
this._actual = new ColorTheme(kind);
this._onDidChangeActiveColorTheme.fire(this._actual);
}
public get onDidChangeActiveColorTheme(): Event<ColorTheme> {
return this._onDidChangeActiveColorTheme.event;
}
}

View file

@ -6,27 +6,20 @@
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as vscode from 'vscode';
import { RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface TunnelOptions {
remote: { port: number, host: string };
localPort?: number;
name?: string;
closeable?: boolean;
}
export interface TunnelDto {
remote: { port: number, host: string };
remoteAddress: { port: number, host: string };
localAddress: string;
}
export namespace TunnelDto {
export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto {
return { remote: tunnel.remote, localAddress: tunnel.localAddress };
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress };
}
export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto {
return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
return { remoteAddress: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
}
}
@ -37,7 +30,7 @@ export interface Tunnel extends vscode.Disposable {
export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
readonly _serviceBrand: undefined;
makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined>;
openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined>;
setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
}
@ -45,10 +38,10 @@ export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IEx
export class ExtHostTunnelService implements IExtHostTunnelService {
_serviceBrand: undefined;
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
async openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
return undefined;
}
async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> {
async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> {
return [];
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }

View file

@ -2508,3 +2508,20 @@ export enum WebviewContentState {
Unchanged = 2,
Dirty = 3,
}
//#region Theming
@es5ClassCompat
export class ColorTheme implements vscode.ColorTheme {
constructor(public readonly kind: ColorThemeKind) {
}
}
export enum ColorThemeKind {
Light = 1,
Dark = 2,
HighContrast = 3
}
//#endregion Theming

View file

@ -13,16 +13,17 @@ import { exec } from 'child_process';
import * as resources from 'vs/base/common/resources';
import * as fs from 'fs';
import { isLinux } from 'vs/base/common/platform';
import { IExtHostTunnelService, TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { asPromise } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
onDispose: Event<void> = this._onDispose.event;
onDidDispose: Event<void> = this._onDispose.event;
constructor(
public readonly remote: { port: number; host: string; },
public readonly remoteAddress: { port: number; host: string; },
public readonly localAddress: string,
private readonly _dispose: () => void) { }
@ -48,11 +49,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
this.registerCandidateFinder();
}
}
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
async openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
const tunnel = await this._proxy.$openTunnel(forward);
if (tunnel) {
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => {
return this._proxy.$closeTunnel(tunnel.remote.port);
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => {
return this._proxy.$closeTunnel(tunnel.remoteAddress);
});
this._register(disposableTunnel);
return disposableTunnel;
@ -65,8 +66,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
if (provider && provider.forwardPort) {
this._forwardPortProvider = provider.forwardPort;
if (provider && provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider();
} else {
this._forwardPortProvider = undefined;
@ -91,11 +92,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
const providedPort = this._forwardPortProvider!(tunnelOptions);
if (providedPort !== undefined) {
return asPromise(() => providedPort).then(tunnel => {
if (!this._extensionTunnels.has(tunnelOptions.remote.host)) {
this._extensionTunnels.set(tunnelOptions.remote.host, new Map());
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
}
this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel);
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port)));
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, tunnel);
this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
});
}
@ -104,12 +105,12 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> {
async $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> {
if (!isLinux) {
return [];
}
const ports: { port: number, detail: string }[] = [];
const ports: { host: string, port: number, detail: string }[] = [];
const tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8');
const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8');
const procSockets: string = await (new Promise(resolve => {
@ -150,7 +151,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
const command = processMap[socketMap[socket].pid].cmd;
if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) {
ports.push({ port, detail: processMap[socketMap[socket].pid].cmd });
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
}
});
@ -177,7 +178,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
const address = row.local_address.split(':');
return {
socket: parseInt(row.inode, 10),
ip: address[0],
ip: this.parseIpAddress(address[0]),
port: parseInt(address[1], 16)
};
}).map(port => [port.port, port])
@ -185,6 +186,17 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
];
}
private parseIpAddress(hex: string): string {
let result = '';
for (let i = hex.length - 2; (i >= 0); i -= 2) {
result += parseInt(hex.substr(i, 2), 16);
if (i !== 0) {
result += '.';
}
}
return result;
}
private loadConnectionTable(stdout: string): Record<string, string>[] {
const lines = stdout.trim().split('\n');
const names = lines.shift()!.trim().split(/\s+/)

View file

@ -16,7 +16,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform';
import { PanelPositionContext } from 'vs/workbench/common/panel';
@ -151,7 +151,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
// Panel Position
this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService);
this.panelPositionContext.set(this.layoutService.getPanelPosition() === Position.RIGHT ? 'right' : 'bottom');
this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition()));
this.registerListeners();
}

View file

@ -14,7 +14,7 @@ import { pathsToEditors } from 'vs/workbench/common/editor';
import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart';
import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart';
import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { Position, Parts, IWorkbenchLayoutService, positionFromString, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -58,6 +58,7 @@ enum Storage {
PANEL_HIDDEN = 'workbench.panel.hidden',
PANEL_POSITION = 'workbench.panel.location',
PANEL_SIZE = 'workbench.panel.size',
PANEL_DIMENSION = 'workbench.panel.dimension',
PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth',
PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight',
@ -178,8 +179,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
position: Position.BOTTOM,
lastNonMaximizedWidth: 300,
lastNonMaximizedHeight: 300,
panelToRestore: undefined as string | undefined,
restored: false
panelToRestore: undefined as string | undefined
},
statusBar: {
@ -571,10 +571,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private updatePanelPosition() {
const defaultPanelPosition = this.configurationService.getValue<string>(Settings.PANEL_POSITION);
const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, undefined);
const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition);
this.state.panel.restored = panelPosition !== undefined;
this.state.panel.position = ((panelPosition || defaultPanelPosition) === 'right') ? Position.RIGHT : Position.BOTTOM;
this.state.panel.position = positionFromString(panelPosition || defaultPanelPosition);
}
registerPart(part: Part): void {
@ -667,15 +666,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
getMaximumEditorDimensions(): Dimension {
const isColumn = this.state.panel.position === Position.RIGHT || this.state.panel.position === Position.LEFT;
const takenWidth =
(this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) +
(this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) +
(this.isVisible(Parts.PANEL_PART) && this.state.panel.position === Position.RIGHT ? this.panelPartView.minimumWidth : 0);
(this.isVisible(Parts.PANEL_PART) && isColumn ? this.panelPartView.minimumWidth : 0);
const takenHeight =
(this.isVisible(Parts.TITLEBAR_PART) ? this.titleBarPartView.minimumHeight : 0) +
(this.isVisible(Parts.STATUSBAR_PART) ? this.statusBarPartView.minimumHeight : 0) +
(this.isVisible(Parts.PANEL_PART) && this.state.panel.position === Position.BOTTOM ? this.panelPartView.minimumHeight : 0);
(this.isVisible(Parts.PANEL_PART) && !isColumn ? this.panelPartView.minimumHeight : 0);
const availableWidth = this.dimension.width - takenWidth;
const availableHeight = this.dimension.height - takenHeight;
@ -899,6 +899,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
: (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width);
this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL);
this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL);
const gridSize = grid.getViewSize();
this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL);
@ -1202,26 +1203,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return this.state.panel.position;
}
setPanelPosition(position: Position.BOTTOM | Position.RIGHT): void {
setPanelPosition(position: Position): void {
if (this.state.panel.hidden) {
this.setPanelHidden(false);
}
const panelPart = this.getPart(Parts.PANEL_PART);
const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right';
const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right';
const oldPositionValue = positionToString(this.state.panel.position);
const newPositionValue = positionToString(position);
this.state.panel.position = position;
function positionToString(position: Position): string {
switch (position) {
case Position.LEFT: return 'left';
case Position.RIGHT: return 'right';
case Position.BOTTOM: return 'bottom';
}
}
// Save panel position
this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE);
this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE);
// Adjust CSS
const panelContainer = assertIsDefined(panelPart.getContainer());
@ -1250,14 +1243,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (position === Position.BOTTOM) {
this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : this.state.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down);
} else {
} else if (position === Position.RIGHT) {
this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Right);
} else {
this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Left);
}
// Reset sidebar to original size before shifting the panel
this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize);
this._onPanelPositionChange.fire(positionToString(this.state.panel.position));
this._onPanelPositionChange.fire(newPositionValue);
}
isWindowMaximized() {
@ -1275,13 +1270,26 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this._onMaximizeChange.fire(maximized);
}
private arrangeEditorNodes(editorNode: ISerializedNode, panelNode: ISerializedNode, editorSectionWidth: number): ISerializedNode[] {
switch (this.state.panel.position) {
case Position.BOTTOM:
return [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }];
case Position.RIGHT:
return [editorNode, panelNode];
case Position.LEFT:
return [panelNode, editorNode];
}
}
private createGridDescriptor(): ISerializedGrid {
const workbenchDimensions = this.getClientArea();
const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width);
const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height);
// At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys
const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300)));
const panelSize = this.state.panel.restored ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)) : workbenchDimensions.height / 3;
const panelDimension = positionFromString(this.storageService.get(Storage.PANEL_DIMENSION, StorageScope.GLOBAL, 'bottom'));
const fallbackPanelSize = this.state.panel.position === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4;
const panelSize = panelDimension === this.state.panel.position ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, fallbackPanelSize)) : fallbackPanelSize;
const titleBarHeight = this.titleBarPartView.minimumHeight;
const statusBarHeight = this.statusBarPartView.minimumHeight;
@ -1319,9 +1327,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
visible: !this.state.panel.hidden
};
const editorSectionNode: ISerializedNode[] = this.state.panel.position === Position.BOTTOM
? [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }]
: [editorNode, panelNode];
const editorSectionNode = this.arrangeEditorNodes(editorNode, panelNode, editorSectionWidth);
const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT
? [activityBarNode, sideBarNode, ...editorSectionNode]

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions } from 'vs/workbench/common/editor';
import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor';
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IDisposable } from 'vs/base/common/lifecycle';
@ -63,11 +63,6 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd
return options;
}
export interface IEditorPartOptionsChangeEvent {
oldPartOptions: IEditorPartOptions;
newPartOptions: IEditorPartOptions;
}
export interface IEditorOpeningEvent extends IEditorIdentifier {
options?: IEditorOptions;
@ -156,4 +151,9 @@ export interface EditorServiceImpl extends IEditorService {
* Emitted when an editor failed to open.
*/
readonly onDidOpenEditorFail: Event<IEditorIdentifier>;
/**
* Emitted when the list of most recently active editors change.
*/
readonly onDidMostRecentlyActiveEditorsChange: Event<void>;
}

View file

@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { mixin } from 'vs/base/common/objects';
import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason } from 'vs/workbench/common/editor';
import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder } from 'vs/workbench/common/editor';
import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
@ -16,7 +16,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX } from 'vs/workbench/browser/parts/editor/editorCommands';
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { DisposableStore } from 'vs/base/common/lifecycle';

View file

@ -6,7 +6,7 @@
import 'vs/css!./media/editorgroupview';
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor';
import { Event, Emitter, Relay } from 'vs/base/common/event';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom';
@ -17,7 +17,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme';
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
@ -31,7 +31,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { 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, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@ -723,7 +723,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
get editors(): EditorInput[] {
return this._group.getEditors();
return this._group.getEditors(EditorsOrder.SEQUENTIAL);
}
get count(): number {
@ -750,12 +750,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this._group.isActive(editor);
}
getEditors(order?: EditorsOrder): EditorInput[] {
if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
return this._group.getEditors(true);
}
return this.editors;
getEditors(order: EditorsOrder): EditorInput[] {
return this._group.getEditors(order);
}
getEditorByIndex(index: number): EditorInput | undefined {
@ -1019,7 +1015,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Use the first editor as active editor
const { editor, options } = editors.shift()!;
let firstOpenedEditor = await this.openEditor(editor, options);
await this.openEditor(editor, options);
// Open the other ones inactive
const startingIndex = this.getIndexOfEditor(editor) + 1;
@ -1029,13 +1025,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
adjustedEditorOptions.pinned = true;
adjustedEditorOptions.index = startingIndex + index;
const openedEditor = await this.openEditor(editor, adjustedEditorOptions);
if (!firstOpenedEditor) {
firstOpenedEditor = openedEditor; // only take if the first editor opening failed
}
await this.openEditor(editor, adjustedEditorOptions);
}));
return firstOpenedEditor;
// Opening many editors at once can put any editor to be
// the active one depending on options. As such, we simply
// return the active control after this operation.
return this.editorControl.activeControl;
}
//#endregion
@ -1371,7 +1367,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const filter = editors;
const hasDirection = typeof filter.direction === 'number';
let editorsToClose = this._group.getEditors(!hasDirection /* in MRU order only if direction is not specified */);
let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE); // in MRU order only if direction is not specified
// Filter: saved only
if (filter.savedOnly) {
@ -1432,7 +1428,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Check for dirty and veto
const editors = this._group.getEditors(true);
const editors = this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);
const veto = await this.handleDirtyClosing(editors.slice(0));
if (veto) {
return;
@ -1526,8 +1522,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#endregion
//#endregion
//#region Themable
protected updateStyles(): void {

View file

@ -12,11 +12,11 @@ import { contrastBorder, editorBackground } from 'vs/platform/theme/common/color
import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid';
import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions } from 'vs/workbench/common/editor';
import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor';
import { values } from 'vs/base/common/map';
import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme';
import { distinct, coalesce } from 'vs/base/common/arrays';
import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor';
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
@ -110,9 +110,12 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
private readonly _onDidMoveGroup = this._register(new Emitter<IEditorGroupView>());
readonly onDidMoveGroup = this._onDidMoveGroup.event;
private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>());
private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>());
get onDidSizeConstraintsChange(): Event<{ width: number; height: number; } | undefined> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); }
private readonly onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>());
private readonly _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>());
readonly onDidSizeConstraintsChange = Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event);
private readonly _onDidEditorPartOptionsChange = this._register(new Emitter<IEditorPartOptionsChangeEvent>());
readonly onDidEditorPartOptionsChange = this._onDidEditorPartOptionsChange.event;
//#endregion
@ -155,13 +158,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.registerListeners();
}
//#region IEditorGroupsAccessor
private enforcedPartOptions: IEditorPartOptions[] = [];
private readonly _onDidEditorPartOptionsChange: Emitter<IEditorPartOptionsChangeEvent> = this._register(new Emitter<IEditorPartOptionsChangeEvent>());
readonly onDidEditorPartOptionsChange: Event<IEditorPartOptionsChangeEvent> = this._onDidEditorPartOptionsChange.event;
private registerListeners(): void {
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
}
@ -185,6 +181,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this._onDidEditorPartOptionsChange.fire({ oldPartOptions, newPartOptions });
}
//#region IEditorGroupsService
private enforcedPartOptions: IEditorPartOptions[] = [];
get partOptions(): IEditorPartOptions {
return this._partOptions;
}
@ -199,10 +199,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
});
}
//#endregion
//#region IEditorGroupsService
private _contentDimension!: Dimension;
get contentDimension(): Dimension { return this._contentDimension; }

View file

@ -13,12 +13,11 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor';
import { toResource, SideBySideEditor, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
export class EditorPickerEntry extends QuickOpenEntryGroup {
@ -209,14 +208,13 @@ export abstract class BaseAllEditorsPicker extends BaseEditorPicker {
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IHistoryService protected historyService: IHistoryService
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(instantiationService, editorService, editorGroupService);
}
protected count(): number {
return this.historyService.getMostRecentlyUsedOpenEditors().length;
return this.editorService.count;
}
getEmptyLabel(searchString: string): string {
@ -262,7 +260,7 @@ export class AllEditorsByMostRecentlyUsedPicker extends BaseAllEditorsPicker {
protected getEditorEntries(): EditorPickerEntry[] {
const entries: EditorPickerEntry[] = [];
for (const { editor, groupId } of this.historyService.getMostRecentlyUsedOpenEditors()) {
for (const { editor, groupId } of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, this.editorGroupService.getGroup(groupId)!));
}

View file

@ -0,0 +1,400 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor';
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
import { Event, Emitter } from 'vs/base/common/event';
import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { coalesce } from 'vs/base/common/arrays';
import { LinkedMap, Touch } from 'vs/base/common/map';
import { equals } from 'vs/base/common/objects';
interface ISerializedEditorsList {
entries: ISerializedEditorIdentifier[];
}
interface ISerializedEditorIdentifier {
groupId: GroupIdentifier;
index: number;
}
/**
* A observer of opened editors across all editor groups by most recently used.
* Rules:
* - the last editor in the list is the one most recently activated
* - the first editor in the list is the one that was activated the longest time ago
* - an editor that opens inactive will be placed behind the currently active editor
*
* The observer may start to close editors based on the workbench.editor.limit setting.
*/
export class EditorsObserver extends Disposable {
private static readonly STORAGE_KEY = 'editors.mru';
private readonly keyMap = new Map<GroupIdentifier, Map<IEditorInput, IEditorIdentifier>>();
private readonly mostRecentEditorsMap = new LinkedMap<IEditorIdentifier, IEditorIdentifier>();
private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
get count(): number {
return this.mostRecentEditorsMap.size;
}
get editors(): IEditorIdentifier[] {
return this.mostRecentEditorsMap.values();
}
constructor(
@IEditorGroupsService private editorGroupsService: IEditorGroupsService,
@IStorageService private readonly storageService: IStorageService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
this._register(this.storageService.onWillSaveState(() => this.saveState()));
this._register(this.editorGroupsService.onDidAddGroup(group => this.onGroupAdded(group)));
this._register(this.editorGroupsService.onDidEditorPartOptionsChange(e => this.onDidEditorPartOptionsChange(e)));
this.editorGroupsService.whenRestored.then(() => this.loadState());
}
private onGroupAdded(group: IEditorGroup): void {
// Make sure to add any already existing editor
// of the new group into our list in LRU order
const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);
for (let i = groupEditorsMru.length - 1; i >= 0; i--) {
this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */);
}
// Make sure that active editor is put as first if group is active
if (this.editorGroupsService.activeGroup === group && group.activeEditor) {
this.addMostRecentEditor(group, group.activeEditor, true /* is active */);
}
// Group Listeners
this.registerGroupListeners(group);
}
private registerGroupListeners(group: IEditorGroup): void {
const groupDisposables = new DisposableStore();
groupDisposables.add(group.onDidGroupChange(e => {
switch (e.kind) {
// Group gets active: put active editor as most recent
case GroupChangeKind.GROUP_ACTIVE: {
if (this.editorGroupsService.activeGroup === group && group.activeEditor) {
this.addMostRecentEditor(group, group.activeEditor, true /* is active */);
}
break;
}
// Editor gets active: put active editor as most recent
// if group is active, otherwise second most recent
case GroupChangeKind.EDITOR_ACTIVE: {
if (e.editor) {
this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group);
}
break;
}
// Editor opens: put it as second most recent
//
// Also check for maximum allowed number of editors and
// start to close oldest ones if needed.
case GroupChangeKind.EDITOR_OPEN: {
if (e.editor) {
this.addMostRecentEditor(group, e.editor, false /* is not active */);
this.ensureOpenedEditorsLimit({ groupId: group.id, editor: e.editor }, group.id);
}
break;
}
// Editor closes: remove from recently opened
case GroupChangeKind.EDITOR_CLOSE: {
if (e.editor) {
this.removeMostRecentEditor(group, e.editor);
}
break;
}
}
}));
// Make sure to cleanup on dispose
Event.once(group.onWillDispose)(() => dispose(groupDisposables));
}
private onDidEditorPartOptionsChange(event: IEditorPartOptionsChangeEvent): void {
if (!equals(event.newPartOptions.limit, event.oldPartOptions.limit)) {
const activeGroup = this.editorGroupsService.activeGroup;
let exclude: IEditorIdentifier | undefined = undefined;
if (activeGroup.activeEditor) {
exclude = { editor: activeGroup.activeEditor, groupId: activeGroup.id };
}
this.ensureOpenedEditorsLimit(exclude);
}
}
private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean): void {
const key = this.ensureKey(group, editor);
const mostRecentEditor = this.mostRecentEditorsMap.first;
// Active or first entry: add to end of map
if (isActive || !mostRecentEditor) {
this.mostRecentEditorsMap.set(key, key, mostRecentEditor ? Touch.AsOld /* make first */ : undefined);
}
// Otherwise: insert before most recent
else {
// we have most recent editors. as such we
// put this newly opened editor right before
// the current most recent one because it cannot
// be the most recently active one unless
// it becomes active. but it is still more
// active then any other editor in the list.
this.mostRecentEditorsMap.set(key, key, Touch.AsOld /* make first */);
this.mostRecentEditorsMap.set(mostRecentEditor, mostRecentEditor, Touch.AsOld /* make first */);
}
// Event
this._onDidChange.fire();
}
private removeMostRecentEditor(group: IEditorGroup, editor: IEditorInput): void {
const key = this.findKey(group, editor);
if (key) {
// Remove from most recent editors
this.mostRecentEditorsMap.delete(key);
// Remove from key map
const map = this.keyMap.get(group.id);
if (map && map.delete(key.editor) && map.size === 0) {
this.keyMap.delete(group.id);
}
// Event
this._onDidChange.fire();
}
}
private findKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier | undefined {
const groupMap = this.keyMap.get(group.id);
if (!groupMap) {
return undefined;
}
return groupMap.get(editor);
}
private ensureKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier {
let groupMap = this.keyMap.get(group.id);
if (!groupMap) {
groupMap = new Map();
this.keyMap.set(group.id, groupMap);
}
let key = groupMap.get(editor);
if (!key) {
key = { groupId: group.id, editor };
groupMap.set(editor, key);
}
return key;
}
private async ensureOpenedEditorsLimit(exclude: IEditorIdentifier | undefined, groupId?: GroupIdentifier): Promise<void> {
if (
!this.editorGroupsService.partOptions.limit?.enabled ||
typeof this.editorGroupsService.partOptions.limit.value !== 'number' ||
this.editorGroupsService.partOptions.limit.value <= 0
) {
return; // return early if not enabled or invalid
}
const limit = this.editorGroupsService.partOptions.limit.value;
// In editor group
if (this.editorGroupsService.partOptions.limit?.perEditorGroup) {
// For specific editor groups
if (typeof groupId === 'number') {
const group = this.editorGroupsService.getGroup(groupId);
if (group) {
await this.doEnsureOpenedEditorsLimit(limit, group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId })), exclude);
}
}
// For all editor groups
else {
for (const group of this.editorGroupsService.groups) {
await this.ensureOpenedEditorsLimit(exclude, group.id);
}
}
}
// Across all editor groups
else {
await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude);
}
}
private async doEnsureOpenedEditorsLimit(limit: number, mostRecentEditors: IEditorIdentifier[], exclude?: IEditorIdentifier): Promise<void> {
if (limit >= mostRecentEditors.length) {
return; // only if opened editors exceed setting and is valid and enabled
}
// Extract least recently used editors that can be closed
const leastRecentlyClosableEditors = mostRecentEditors.reverse().filter(({ editor, groupId }) => {
if (editor.isDirty()) {
return false; // not dirty editors
}
if (exclude && editor === exclude.editor && groupId === exclude.groupId) {
return false; // never the editor that should be excluded
}
return true;
});
// Close editors until we reached the limit again
let editorsToCloseCount = mostRecentEditors.length - limit;
const mapGroupToEditorsToClose = new Map<GroupIdentifier, IEditorInput[]>();
for (const { groupId, editor } of leastRecentlyClosableEditors) {
let editorsInGroupToClose = mapGroupToEditorsToClose.get(groupId);
if (!editorsInGroupToClose) {
editorsInGroupToClose = [];
mapGroupToEditorsToClose.set(groupId, editorsInGroupToClose);
}
editorsInGroupToClose.push(editor);
editorsToCloseCount--;
if (editorsToCloseCount === 0) {
break; // limit reached
}
}
for (const [groupId, editors] of mapGroupToEditorsToClose) {
const group = this.editorGroupsService.getGroup(groupId);
if (group) {
await group.closeEditors(editors, { preserveFocus: true });
}
}
}
private saveState(): void {
if (this.mostRecentEditorsMap.isEmpty()) {
this.storageService.remove(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE);
} else {
this.storageService.store(EditorsObserver.STORAGE_KEY, JSON.stringify(this.serialize()), StorageScope.WORKSPACE);
}
}
private serialize(): ISerializedEditorsList {
const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
const entries = this.mostRecentEditorsMap.values();
const mapGroupToSerializableEditorsOfGroup = new Map<IEditorGroup, IEditorInput[]>();
return {
entries: coalesce(entries.map(({ editor, groupId }) => {
// Find group for entry
const group = this.editorGroupsService.getGroup(groupId);
if (!group) {
return undefined;
}
// Find serializable editors of group
let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group);
if (!serializableEditorsOfGroup) {
serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => {
const factory = registry.getEditorInputFactory(editor.getTypeId());
return factory?.canSerialize(editor);
});
mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup);
}
// Only store the index of the editor of that group
// which can be undefined if the editor is not serializable
const index = serializableEditorsOfGroup.indexOf(editor);
if (index === -1) {
return undefined;
}
return { groupId, index };
}))
};
}
private loadState(): void {
const serialized = this.storageService.get(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE);
// Previous state:
if (serialized) {
// Load editors map from persisted state
this.deserialize(JSON.parse(serialized));
}
// No previous state: best we can do is add each editor
// from oldest to most recently used editor group
else {
const groups = this.editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
for (let i = groups.length - 1; i >= 0; i--) {
const group = groups[i];
const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);
for (let i = groupEditorsMru.length - 1; i >= 0; i--) {
this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */);
}
}
}
// Ensure we listen on group changes for those that exist on startup
for (const group of this.editorGroupsService.groups) {
this.registerGroupListeners(group);
}
}
private deserialize(serialized: ISerializedEditorsList): void {
const mapValues: [IEditorIdentifier, IEditorIdentifier][] = [];
for (const { groupId, index } of serialized.entries) {
// Find group for entry
const group = this.editorGroupsService.getGroup(groupId);
if (!group) {
continue;
}
// Find editor for entry
const editor = group.getEditorByIndex(index);
if (!editor) {
continue;
}
// Make sure key is registered as well
const editorIdentifier = this.ensureKey(group, editor);
mapValues.push([editorIdentifier, editorIdentifier]);
}
// Fill map with deserialized values
this.mostRecentEditorsMap.fromJSON(mapValues);
}
}

View file

@ -37,6 +37,15 @@
border-left-width: 0; /* no border when editor area is hiden */
}
.monaco-workbench .part.panel.left {
border-right-width: 1px;
border-right-style: solid;
}
.monaco-workbench.noeditorarea .part.panel.left {
border-right-width: 0; /* no border when editor area is hiden */
}
.monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label {
outline-offset: -2px;
}
@ -121,3 +130,10 @@
.monaco-workbench .part.panel.right .title-actions .codicon-chevron-down {
transform: rotate(-90deg);
}
/* Rotate icons when panel is on left */
.monaco-workbench .part.panel.left .title-actions .codicon-split-horizontal,
.monaco-workbench .part.panel.left .title-actions .codicon-chevron-up,
.monaco-workbench .part.panel.left .title-actions .codicon-chevron-down {
transform: rotate(90deg);
}

View file

@ -12,11 +12,12 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { IActivity } from 'vs/workbench/common/activity';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
export class ClosePanelAction extends Action {
@ -88,42 +89,6 @@ class FocusPanelAction extends Action {
}
}
export class TogglePanelPositionAction extends Action {
static readonly ID = 'workbench.action.togglePanelPosition';
static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position");
static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right");
static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom");
private readonly toDispose = this._register(new DisposableStore());
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IEditorGroupsService editorGroupsService: IEditorGroupsService
) {
super(id, label, layoutService.getPanelPosition() === Position.RIGHT ? 'move-panel-to-bottom' : 'move-panel-to-right');
const setClassAndLabel = () => {
const positionRight = this.layoutService.getPanelPosition() === Position.RIGHT;
this.class = positionRight ? 'move-panel-to-bottom' : 'move-panel-to-right';
this.label = positionRight ? TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL : TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL;
};
this.toDispose.add(editorGroupsService.onDidLayout(() => setClassAndLabel()));
setClassAndLabel();
}
run(): Promise<any> {
const position = this.layoutService.getPanelPosition();
this.layoutService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM);
return Promise.resolve();
}
}
export class ToggleMaximizedPanelAction extends Action {
@ -160,6 +125,54 @@ export class ToggleMaximizedPanelAction extends Action {
}
}
const PositionPanelActionId = {
LEFT: 'workbench.action.positionPanelLeft',
RIGHT: 'workbench.action.positionPanelRight',
BOTTOM: 'workbench.action.positionPanelBottom',
};
interface PanelActionConfig<T> {
id: string;
when: ContextKeyExpr;
alias: string;
label: string;
value: T;
}
function createPositionPanelActionConfig(id: string, alias: string, label: string, position: Position): PanelActionConfig<Position> {
return {
id,
alias,
label,
value: position,
when: PanelPositionContext.notEqualsTo(positionToString(position))
};
}
export const PositionPanelActionConfigs = [
createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Panel Position Left', nls.localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT),
createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Panel Position Right', nls.localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT),
createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Panel Position Bottom', nls.localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM),
];
const positionByActionId = new Map(PositionPanelActionConfigs.map(config => [config.id, config.value]));
export class SetPanelPositionAction extends Action {
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super(id, label);
}
run(): Promise<any> {
const position = positionByActionId.get(this.id);
this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position);
return Promise.resolve();
}
}
export class PanelActivityAction extends ActivityAction {
constructor(
@ -247,7 +260,6 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelAc
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Previous Panel View', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Next Panel View', nls.localize('view', "View"));
@ -262,22 +274,21 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
order: 5
});
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move',
command: {
id: TogglePanelPositionAction.ID,
title: TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL
},
when: PanelPositionContext.isEqualTo('bottom'),
order: 5
});
function registerPositionPanelActionById(config: PanelActionConfig<Position>) {
const { id, label, alias, when } = config;
// register the workbench action
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetPanelPositionAction, id, label), alias, nls.localize('view', "View"), when);
// register as a menu item
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move',
command: {
id,
title: label
},
when,
order: 5
});
}
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move',
command: {
id: TogglePanelPositionAction.ID,
title: TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL
},
when: PanelPositionContext.isEqualTo('right'),
order: 5
});
// register each position panel action
PositionPanelActionConfigs.forEach(registerPositionPanelActionById);

View file

@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions';
import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
@ -52,7 +52,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
//#region IView
readonly minimumWidth: number = 300;
readonly minimumWidth: number = 420;
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
readonly minimumHeight: number = 77;
readonly maximumHeight: number = Number.POSITIVE_INFINITY;
@ -122,7 +122,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
getContextMenuActions: () => [
this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL),
...PositionPanelActionConfigs
// show the contextual menu item if it is not in that position
.filter(({ when }) => contextKeyService.contextMatchesRules(when))
.map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)),
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
],
getDefaultCompositeId: () => Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
@ -207,7 +210,9 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
const container = assertIsDefined(this.getContainer());
container.style.backgroundColor = this.getColor(PANEL_BACKGROUND) || '';
container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || '';
const borderColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || '';
container.style.borderLeftColor = borderColor;
container.style.borderRightColor = borderColor;
const title = this.getTitleArea();
if (title) {

View file

@ -44,6 +44,7 @@
line-height: 22px;
height: 100%;
vertical-align: top;
max-width: 40vw;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak {
@ -94,7 +95,9 @@
height: 100%;
padding: 0 5px 0 5px;
white-space: pre; /* gives some degree of styling */
align-items: center
align-items: center;
text-overflow: ellipsis;
overflow: hidden;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a:hover {

View file

@ -495,14 +495,12 @@ export class CustomTreeView extends Disposable implements ITreeView {
private showMessage(message: string): void {
DOM.removeClass(this.messageElement, 'hide');
if (this._messageValue !== message) {
this.resetMessageElement();
this._messageValue = message;
if (!isFalsyOrWhitespace(this._message)) {
this.messageElement.textContent = this._messageValue;
}
this.layout(this._height, this._width);
this.resetMessageElement();
this._messageValue = message;
if (!isFalsyOrWhitespace(this._message)) {
this.messageElement.textContent = this._messageValue;
}
this.layout(this._height, this._width);
}
private hideMessage(): void {

View file

@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor } from 'vs/workbench/common/views';
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer } from 'vs/workbench/common/views';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types';
@ -36,8 +36,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer';
import { Component } from 'vs/workbench/common/component';
import { Extensions as ViewletExtensions, ViewletRegistry } from 'vs/workbench/browser/viewlet';
import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/browser/panel';
export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier;
@ -234,7 +232,8 @@ export abstract class ViewPane extends Pane implements IView {
}
export interface IViewPaneContainerOptions extends IPaneViewOptions {
showHeaderInTitleWhenSingleView: boolean;
mergeViewWithContainerWhenSingleView: boolean;
donotShowContainerTitleWhenMergedWithContainer?: boolean;
}
interface IViewPaneItem {
@ -244,6 +243,7 @@ interface IViewPaneItem {
export class ViewPaneContainer extends Component implements IViewPaneContainer {
private readonly viewContainer: ViewContainer;
private lastFocusedPane: ViewPane | undefined;
private paneItems: IViewPaneItem[] = [];
private paneview?: PaneView;
@ -308,6 +308,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.options.dnd = new DefaultPaneDndController();
}
this.viewContainer = container;
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined);
this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables)));
@ -345,15 +346,15 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
getTitle(): string {
const composite = Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).getViewlet(this.getId()) || Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanel(this.getId());
let title = composite.name;
if (this.isSingleView()) {
if (this.isViewMergedWithContainer()) {
const paneItemTitle = this.paneItems[0].pane.title;
title = paneItemTitle ? `${title}: ${paneItemTitle}` : title;
if (this.options.donotShowContainerTitleWhenMergedWithContainer || this.viewContainer.name === paneItemTitle) {
return this.paneItems[0].pane.title;
}
return paneItemTitle ? `${this.viewContainer.name}: ${paneItemTitle}` : this.viewContainer.name;
}
return title;
return this.viewContainer.name;
}
private showContextMenu(event: StandardMouseEvent): void {
@ -402,7 +403,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
getActions(): IAction[] {
if (this.isSingleView()) {
if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getActions();
}
@ -410,7 +411,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
getSecondaryActions(): IAction[] {
if (this.isSingleView()) {
if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getSecondaryActions();
}
@ -418,7 +419,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
if (this.isSingleView()) {
if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getActionViewItem(action);
}
@ -459,14 +460,14 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
addPanes(panes: { pane: ViewPane, size: number, index?: number; }[]): void {
const wasSingleView = this.isSingleView();
const wasMerged = this.isViewMergedWithContainer();
for (const { pane: pane, size, index } of panes) {
this.addPane(pane, size, index);
}
this.updateViewHeaders();
if (this.isSingleView() !== wasSingleView) {
if (this.isViewMergedWithContainer() !== wasMerged) {
this.updateTitleArea();
}
}
@ -643,7 +644,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void {
const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane);
const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => {
if (this.isSingleView()) {
if (this.isViewMergedWithContainer()) {
this.updateTitleArea();
}
});
@ -668,12 +669,12 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
removePanes(panes: ViewPane[]): void {
const wasSingleView = this.isSingleView();
const wasMerged = this.isViewMergedWithContainer();
panes.forEach(pane => this.removePane(pane));
this.updateViewHeaders();
if (wasSingleView !== this.isSingleView()) {
if (wasMerged !== this.isViewMergedWithContainer()) {
this.updateTitleArea();
}
}
@ -726,8 +727,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return assertIsDefined(this.paneview).getPaneSize(pane);
}
protected updateViewHeaders(): void {
if (this.isSingleView()) {
private updateViewHeaders(): void {
if (this.isViewMergedWithContainer()) {
this.paneItems[0].pane.setExpanded(true);
this.paneItems[0].pane.headerVisible = false;
} else {
@ -735,8 +736,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
}
protected isSingleView(): boolean {
if (!(this.options.showHeaderInTitleWhenSingleView && this.paneItems.length === 1)) {
private isViewMergedWithContainer(): boolean {
if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) {
return false;
}
if (!this.areExtensionsReady) {

View file

@ -655,8 +655,8 @@ export class ViewsService extends Disposable implements IViewsService {
this.viewDisposable.forEach(disposable => disposable.dispose());
this.viewDisposable.clear();
}));
this._register(viewContainersRegistry.onDidRegister(viewContainer => this.onDidRegisterViewContainer(viewContainer)));
this._register(viewContainersRegistry.onDidDeregister(viewContainer => this.onDidDeregisterViewContainer(viewContainer)));
this._register(viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer)));
this._register(viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer)));
this._register(toDisposable(() => {
this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose());
this.viewDescriptorCollections.clear();

View file

@ -46,7 +46,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
@IWorkspaceContextService contextService: IWorkspaceContextService
) {
super(viewletId, `${viewletId}.state`, { showHeaderInTitleWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
this._register(onDidChangeFilterValue(newFilterValue => {
this.filterValue = newFilterValue;
this.onFilterChanged(newFilterValue);

View file

@ -131,6 +131,22 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
'default': true,
'description': nls.localize('centeredLayoutAutoResize', "Controls if the centered layout should automatically resize to maximum width when more than one group is open. Once only one group is open it will resize back to the original centered width.")
},
'workbench.editor.limit.enabled': {
'type': 'boolean',
'default': false,
'description': nls.localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors that are not dirty will close to make space for newly opening editors.")
},
'workbench.editor.limit.value': {
'type': 'number',
'default': 10,
'exclusiveMinimum': 0,
'description': nls.localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the `workbench.editor.limit.perEditorGroup` setting to control this limit per editor group or across all groups.")
},
'workbench.editor.limit.perEditorGroup': {
'type': 'boolean',
'default': false,
'description': nls.localize('perEditorGroup', "Controls if the limit of maximum opened editors should apply per editor group or across all editor groups.")
},
'workbench.commandPalette.history': {
'type': 'number',
'description': nls.localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."),
@ -174,7 +190,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
},
'workbench.panel.defaultLocation': {
'type': 'string',
'enum': ['bottom', 'right'],
'enum': ['left', 'bottom', 'right'],
'default': 'bottom',
'description': nls.localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom or on the right of the workbench.")
},
@ -207,24 +223,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
],
'included': isMacintosh
},
'workbench.settings.enableNaturalLanguageSearch': {
'type': 'boolean',
'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."),
'default': true,
'scope': ConfigurationScope.WINDOW,
'tags': ['usesOnlineServices']
},
'workbench.settings.settingsSearchTocBehavior': {
'type': 'string',
'enum': ['hide', 'filter'],
'enumDescriptions': [
nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."),
nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."),
],
'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."),
'default': 'filter',
'scope': ConfigurationScope.WINDOW
},
'workbench.settings.editor': {
'type': 'string',
'enum': ['ui', 'json'],
@ -235,12 +233,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."),
'default': 'ui',
'scope': ConfigurationScope.WINDOW
},
'workbench.enableExperiments': {
'type': 'boolean',
'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."),
'default': true,
'tags': ['usesOnlineServices']
}
}
});

View file

@ -18,7 +18,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@ -349,7 +349,7 @@ export class Workbench extends Layout {
{ id: Parts.ACTIVITYBAR_PART, role: 'navigation', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },
{ id: Parts.SIDEBAR_PART, role: 'complementary', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },
{ id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } },
{ id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', this.state.panel.position === Position.BOTTOM ? 'bottom' : 'right'] },
{ id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', positionToString(this.state.panel.position)] },
{ id: Parts.STATUSBAR_PART, role: 'contentinfo', classes: ['statusbar'] }
].forEach(({ id, role, classes, options }) => {
const partContainer = this.createPart(id, role, classes);

View file

@ -1171,12 +1171,22 @@ interface IEditorPartConfiguration {
labelFormat?: 'default' | 'short' | 'medium' | 'long';
restoreViewState?: boolean;
splitSizing?: 'split' | 'distribute';
limit?: {
enabled?: boolean;
value?: number;
perEditorGroup?: boolean;
};
}
export interface IEditorPartOptions extends IEditorPartConfiguration {
iconTheme?: string;
}
export interface IEditorPartOptionsChangeEvent {
oldPartOptions: IEditorPartOptions;
newPartOptions: IEditorPartOptions;
}
export enum SideBySideEditor {
MASTER = 1,
DETAILS = 2
@ -1317,3 +1327,16 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService
return coalesce(editors);
}
export const enum EditorsOrder {
/**
* Editors sorted by most recent activity (most recent active first)
*/
MOST_RECENTLY_ACTIVE,
/**
* Editors sorted by sequential order
*/
SEQUENTIAL
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, SideBySideEditorInput, IEditorInput } from 'vs/workbench/common/editor';
import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
@ -130,8 +130,8 @@ export class EditorGroup extends Disposable {
return this.editors.length;
}
getEditors(mru?: boolean): EditorInput[] {
return mru ? this.mru.slice(0) : this.editors.slice(0);
getEditors(order: EditorsOrder): EditorInput[] {
return order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0);
}
getEditorByIndex(index: number): EditorInput | undefined {

View file

@ -12,11 +12,12 @@ import { IViewlet } from 'vs/workbench/common/viewlet';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { values, keys } from 'vs/base/common/map';
import { values, keys, getOrSet } from 'vs/base/common/map';
import { Registry } from 'vs/platform/registry/common/platform';
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IAction } from 'vs/base/common/actions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { flatten } from 'vs/base/common/arrays';
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
export const FocusedViewContext = new RawContextKey<string>('focusedView', '');
@ -31,16 +32,30 @@ export enum ViewContainerLocation {
Panel
}
export interface IViewContainerDescriptor {
readonly id: string;
readonly name: string;
readonly viewOrderDelegate?: ViewOrderDelegate;
readonly hideIfEmpty?: boolean;
readonly extensionId?: ExtensionIdentifier;
}
export interface IViewContainersRegistry {
/**
* An event that is triggerred when a view container is registered.
*/
readonly onDidRegister: Event<ViewContainer>;
readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
/**
* An event that is triggerred when a view container is deregistered.
*/
readonly onDidDeregister: Event<ViewContainer>;
readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
/**
* All registered view containers
@ -48,14 +63,15 @@ export interface IViewContainersRegistry {
readonly all: ViewContainer[];
/**
* Registers a view container with given id
* No op if a view container is already registered with the given id.
* Registers a view container to given location.
* No op if a view container is already registered.
*
* @param id of the view container.
* @param viewContainerDescriptor descriptor of view container
* @param location location of the view container
*
* @returns the registered ViewContainer.
*/
registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer;
registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation): ViewContainer;
/**
* Deregisters the given view container
@ -69,6 +85,11 @@ export interface IViewContainersRegistry {
* @returns the view container with given id.
*/
get(id: string): ViewContainer | undefined;
/**
* Returns all view containers in the given location
*/
getViewContainers(location: ViewContainerLocation): ViewContainer[];
}
interface ViewOrderDelegate {
@ -76,49 +97,68 @@ interface ViewOrderDelegate {
}
export class ViewContainer {
protected constructor(readonly id: string, readonly location: ViewContainerLocation, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { }
protected constructor(private readonly descriptor: IViewContainerDescriptor) { }
readonly id: string = this.descriptor.id;
readonly name: string = this.descriptor.name;
readonly hideIfEmpty: boolean = !!this.descriptor.hideIfEmpty;
readonly extensionId: ExtensionIdentifier | undefined = this.descriptor.extensionId;
readonly orderDelegate: ViewOrderDelegate | undefined = this.descriptor.viewOrderDelegate;
}
class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry {
private readonly _onDidRegister = this._register(new Emitter<ViewContainer>());
readonly onDidRegister: Event<ViewContainer> = this._onDidRegister.event;
private readonly _onDidRegister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>());
readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidRegister.event;
private readonly _onDidDeregister = this._register(new Emitter<ViewContainer>());
readonly onDidDeregister: Event<ViewContainer> = this._onDidDeregister.event;
private readonly _onDidDeregister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>());
readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidDeregister.event;
private viewContainers: Map<string, ViewContainer> = new Map<string, ViewContainer>();
private viewContainers: Map<ViewContainerLocation, ViewContainer[]> = new Map<ViewContainerLocation, ViewContainer[]>();
get all(): ViewContainer[] {
return values(this.viewContainers);
return flatten(values(this.viewContainers));
}
registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer {
const existing = this.viewContainers.get(id);
registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation): ViewContainer {
const existing = this.get(viewContainerDescriptor.id);
if (existing) {
return existing;
}
const viewContainer = new class extends ViewContainer {
constructor() {
super(id, location, !!hideIfEmpty, extensionId, viewOrderDelegate);
super(viewContainerDescriptor);
}
};
this.viewContainers.set(id, viewContainer);
this._onDidRegister.fire(viewContainer);
const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []);
viewContainers.push(viewContainer);
this._onDidRegister.fire({ viewContainer, viewContainerLocation });
return viewContainer;
}
deregisterViewContainer(viewContainer: ViewContainer): void {
const existing = this.viewContainers.get(viewContainer.id);
if (existing) {
this.viewContainers.delete(viewContainer.id);
this._onDidDeregister.fire(viewContainer);
for (const viewContainerLocation of keys(this.viewContainers)) {
const viewContainers = this.viewContainers.get(viewContainerLocation)!;
const index = viewContainers?.indexOf(viewContainer);
if (index !== -1) {
viewContainers?.splice(index, 1);
if (viewContainers.length === 0) {
this.viewContainers.delete(viewContainerLocation);
}
this._onDidDeregister.fire({ viewContainer, viewContainerLocation });
return;
}
}
}
get(id: string): ViewContainer | undefined {
return this.viewContainers.get(id);
return this.all.filter(viewContainer => viewContainer.id === id)[0];
}
getViewContainers(location: ViewContainerLocation): ViewContainer[] {
return [...(this.viewContainers.get(location) || [])];
}
}

View file

@ -93,7 +93,9 @@ suite('BackupModelRestorer', () => {
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
});
test('Restore backups', async () => {
test('Restore backups', async function () {
this.timeout(20000);
const backupFileService = new NodeTestBackupFileService(workspaceBackupPath);
const instantiationService = workbenchInstantiationService();
instantiationService.stub(IBackupFileService, backupFileService);
@ -120,7 +122,7 @@ suite('BackupModelRestorer', () => {
// Verify backups restored and opened as dirty
await restorer.doRestoreBackups();
assert.equal(editorService.editors.length, 4);
assert.equal(editorService.count, 4);
assert.ok(editorService.editors.every(editor => editor.isDirty()));
let counter = 0;

View file

@ -34,7 +34,8 @@ export function getSimpleEditorOptions(): IEditorOptions {
acceptSuggestionOnEnter: 'smart',
minimap: {
enabled: false
}
},
renderIndentGuides: false
};
}

View file

@ -83,7 +83,7 @@ class OpenDebugPanelAction extends TogglePanelAction {
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
DebugViewlet,
VIEWLET_ID,
nls.localize('debugAndRun', "Debug and Run"),
VIEW_CONTAINER.name,
'codicon-debug-alt',
3
));

View file

@ -157,16 +157,14 @@ export class StartDebugActionViewItem implements IActionViewItem {
this.selected = 0;
this.options = [];
const manager = this.debugService.getConfigurationManager();
const launches = manager.getLaunches();
const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
launches.forEach(launch =>
launch.getConfigurationNames().forEach(name => {
if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {
this.selected = this.options.length;
}
const label = inWorkspace ? `${name} (${launch.name})` : name;
this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } });
}));
manager.getAllConfigurations().forEach(({ launch, name }) => {
if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {
this.selected = this.options.length;
}
const label = inWorkspace ? `${name} (${launch.name})` : name;
this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } });
});
if (this.options.length === 0) {
this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: () => false });
@ -175,7 +173,7 @@ export class StartDebugActionViewItem implements IActionViewItem {
}
const disabledIdx = this.options.length - 1;
launches.filter(l => !l.hidden).forEach(l => {
manager.getLaunches().filter(l => !l.hidden).forEach(l => {
const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
this.options.push({
label, handler: () => {

View file

@ -7,12 +7,13 @@ import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession, ILaunch } from 'vs/workbench/contrib/debug/common/debug';
import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
export abstract class AbstractDebugAction extends Action {
@ -60,7 +61,8 @@ export class ConfigureAction extends AbstractDebugAction {
@IDebugService debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@INotificationService private readonly notificationService: INotificationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService);
this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass()));
@ -77,8 +79,7 @@ export class ConfigureAction extends AbstractDebugAction {
private updateClass(): void {
const configurationManager = this.debugService.getConfigurationManager();
const configurationCount = configurationManager.getLaunches().map(l => l.getConfigurationNames().length).reduce((sum, current) => sum + current);
this.class = configurationCount > 0 ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification';
this.class = configurationManager.selectedConfiguration.name ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification';
}
async run(event?: any): Promise<any> {
@ -87,10 +88,26 @@ export class ConfigureAction extends AbstractDebugAction {
return;
}
const sideBySide = !!(event && (event.ctrlKey || event.metaKey));
const configurationManager = this.debugService.getConfigurationManager();
if (configurationManager.selectedConfiguration.launch) {
return configurationManager.selectedConfiguration.launch.openConfigFile(sideBySide, false);
let launch: ILaunch | undefined;
if (configurationManager.selectedConfiguration.name) {
launch = configurationManager.selectedConfiguration.launch;
} else {
const launches = configurationManager.getLaunches().filter(l => !!l.workspace);
if (launches.length === 1) {
launch = launches[0];
} else {
const picks = launches.map(l => ({ label: l.name, launch: l }));
const picked = await this.quickInputService.pick<{ label: string, launch: ILaunch }>(picks, { activeItem: picks[0], placeHolder: nls.localize('selectWorkspaceFolder', "Select a workspace folder to create a launch.json file in") });
if (picked) {
launch = picked.launch;
}
}
}
if (launch) {
const sideBySide = !!(event && (event.ctrlKey || event.metaKey));
return launch.openConfigFile(sideBySide, false);
}
}
}
@ -127,7 +144,7 @@ export class StartAction extends AbstractDebugAction {
if (debugService.state === State.Initializing) {
return false;
}
if ((sessions.length > 0) && debugService.getConfigurationManager().getLaunches().every(l => l.getConfigurationNames().length === 0)) {
if ((sessions.length > 0) && !debugService.getConfigurationManager().selectedConfiguration.name) {
// There is already a debug session running and we do not have any launch configuration selected
return false;
}

View file

@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
@ -68,8 +68,8 @@ export class ConfigurationManager implements IConfigurationManager {
@ICommandService private readonly commandService: ICommandService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,
@IContextKeyService contextKeyService: IContextKeyService,
@IHistoryService historyService: IHistoryService
@IHistoryService private readonly historyService: IHistoryService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.configProviders = [];
this.adapterDescriptorFactories = [];
@ -83,13 +83,7 @@ export class ConfigurationManager implements IConfigurationManager {
if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) {
this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
} else if (this.launches.length > 0) {
const rootUri = historyService.getLastActiveWorkspaceRoot();
let launch = this.getLaunch(rootUri);
if (!launch || launch.getConfigurationNames().length === 0) {
launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0];
}
this.selectConfiguration(launch);
this.selectConfiguration(undefined);
}
}
@ -220,6 +214,43 @@ export class ConfigurationManager implements IConfigurationManager {
return results.reduce((first, second) => first.concat(second), []);
}
getAllConfigurations(): { launch: ILaunch; name: string; }[] {
const all: { launch: ILaunch, name: string, presentation?: IConfigPresentation }[] = [];
for (let l of this.launches) {
for (let name of l.getConfigurationNames()) {
const config = l.getConfiguration(name) || l.getCompound(name);
if (config && !config.presentation?.hidden) {
all.push({ launch: l, name, presentation: config.presentation });
}
}
}
return all.sort((first, second) => {
if (!first.presentation) {
return 1;
}
if (!second.presentation) {
return -1;
}
if (!first.presentation.group) {
return 1;
}
if (!second.presentation.group) {
return -1;
}
if (first.presentation.group !== second.presentation.group) {
return first.presentation.group.localeCompare(second.presentation.group);
}
if (typeof first.presentation.order !== 'number') {
return 1;
}
if (typeof second.presentation.order !== 'number') {
return -1;
}
return first.presentation.order - second.presentation.order;
});
}
private registerListeners(): void {
debuggersExtPoint.setHandler((extensions, delta) => {
delta.added.forEach(added => {
@ -286,13 +317,13 @@ export class ConfigurationManager implements IConfigurationManager {
this.toDispose.push(Event.any<IWorkspaceFoldersChangeEvent | WorkbenchState>(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => {
this.initLaunches();
const toSelect = this.selectedLaunch || (this.launches.length > 0 ? this.launches[0] : undefined);
this.selectConfiguration(toSelect);
this.selectConfiguration(undefined);
this.setCompoundSchemaValues();
}));
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('launch')) {
this.selectConfiguration(this.selectedLaunch);
// A change happen in the launch.json. If there is already a launch configuration selected, do not change the selection.
this.selectConfiguration(undefined);
this.setCompoundSchemaValues();
}
}));
@ -306,7 +337,7 @@ export class ConfigurationManager implements IConfigurationManager {
this.launches.push(this.instantiationService.createInstance(UserLaunch));
if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) {
this.setSelectedLaunch(undefined);
this.selectConfiguration(undefined);
}
}
@ -355,10 +386,23 @@ export class ConfigurationManager implements IConfigurationManager {
}
selectConfiguration(launch: ILaunch | undefined, name?: string): void {
if (typeof launch === 'undefined') {
const rootUri = this.historyService.getLastActiveWorkspaceRoot();
launch = this.getLaunch(rootUri);
if (!launch || launch.getConfigurationNames().length === 0) {
launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0];
}
}
const previousLaunch = this.selectedLaunch;
const previousName = this.selectedName;
this.selectedLaunch = launch;
this.setSelectedLaunch(launch);
if (this.selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
} else {
this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
}
const names = launch ? launch.getConfigurationNames() : [];
if (name && names.indexOf(name) >= 0) {
this.setSelectedLaunchName(name);
@ -467,16 +511,6 @@ export class ConfigurationManager implements IConfigurationManager {
}
}
private setSelectedLaunch(selectedLaunch: ILaunch | undefined): void {
this.selectedLaunch = selectedLaunch;
if (this.selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
} else {
this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
}
}
dispose(): void {
this.toDispose = dispose(this.toDispose);
}

View file

@ -96,18 +96,18 @@ export class DebugQuickOpenHandler extends QuickOpenHandler {
const configurations: QuickOpenEntry[] = [];
const configManager = this.debugService.getConfigurationManager();
const launches = configManager.getLaunches();
for (let launch of launches) {
launch.getConfigurationNames().map(config => ({ config: config, highlights: matchesFuzzy(input, config, true) || undefined }))
.filter(({ highlights }) => !!highlights)
.forEach(({ config, highlights }) => {
if (launch === configManager.selectedConfiguration.launch && config === configManager.selectedConfiguration.name) {
this.autoFocusIndex = configurations.length;
}
configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.notificationService, launch, config, highlights));
});
const allConfigurations = configManager.getAllConfigurations();
for (let config of allConfigurations) {
const highlights = matchesFuzzy(input, config.name, true);
if (highlights) {
if (config.launch === configManager.selectedConfiguration.launch && config.name === configManager.selectedConfiguration.name) {
this.autoFocusIndex = configurations.length;
}
configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.notificationService, config.launch, config.name, highlights));
}
}
launches.filter(l => !l.hidden).forEach((l, index) => {
configManager.getLaunches().filter(l => !l.hidden).forEach((l, index) => {
const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, matchesFuzzy(input, label, true) || undefined);

View file

@ -79,7 +79,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@INotificationService private readonly notificationService: INotificationService
) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state)));
this._register(this.debugService.onDidNewSession(() => this.updateToolBar()));

Some files were not shown because too many files have changed in this diff Show more