mirror of
https://github.com/Microsoft/vscode
synced 2024-10-12 22:37:41 +00:00
Merge branch 'master' into joh/bulkEditPreview
This commit is contained in:
commit
ddb6d058f7
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -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/ -->
|
||||
|
||||
|
|
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -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. -->
|
||||
|
|
6
.github/commands.yml
vendored
6
.github/commands.yml
vendored
|
@ -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
4
.github/copycat.yml
vendored
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
perform: true,
|
||||
perform: false,
|
||||
target_owner: 'chrmarti',
|
||||
target_repo: 'testissues'
|
||||
}
|
||||
}
|
||||
|
|
112
.vscode/launch.json
vendored
112
.vscode/launch.json
vendored
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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--;
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>',
|
||||
|
|
|
@ -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>',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
|
@ -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 {
|
||||
|
|
|
@ -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": [{
|
||||
|
|
|
@ -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",
|
||||
|
|
9
src/typings/vscode-windows-registry.d.ts
vendored
9
src/typings/vscode-windows-registry.d.ts
vendored
|
@ -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;
|
||||
}
|
|
@ -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-'] {
|
||||
|
|
Binary file not shown.
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import * as assert from 'assert';
|
||||
import * as collections from 'vs/base/common/collections';
|
||||
|
||||
|
||||
suite('Collections', () => {
|
||||
|
||||
test('forEach', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
|
||||
|
|
|
@ -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}; }`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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))));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) { }
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.');
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
62
src/vs/vscode.proposed.d.ts
vendored
62
src/vs/vscode.proposed.d.ts
vendored
|
@ -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>;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import './mainThreadStatusBar';
|
|||
import './mainThreadStorage';
|
||||
import './mainThreadTelemetry';
|
||||
import './mainThreadTerminalService';
|
||||
import './mainThreadTheming';
|
||||
import './mainThreadTreeViews';
|
||||
import './mainThreadDownloadService';
|
||||
import './mainThreadUrls';
|
||||
|
|
33
src/vs/workbench/api/browser/mainThreadTheming.ts
Normal file
33
src/vs/workbench/api/browser/mainThreadTheming.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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 });
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
|||
value: {
|
||||
authority,
|
||||
options,
|
||||
tunnelInformation: { detectedTunnels: result.detectedTunnels }
|
||||
tunnelInformation: { environmentTunnels: result.environmentTunnels }
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
|
|
38
src/vs/workbench/api/common/extHostTheming.ts
Normal file
38
src/vs/workbench/api/common/extHostTheming.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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: () => { } }; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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+/)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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)!));
|
||||
}
|
||||
|
||||
|
|
400
src/vs/workbench/browser/parts/editor/editorsObserver.ts
Normal file
400
src/vs/workbench/browser/parts/editor/editorsObserver.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) || [])];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -34,7 +34,8 @@ export function getSimpleEditorOptions(): IEditorOptions {
|
|||
acceptSuggestionOnEnter: 'smart',
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
renderIndentGuides: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
));
|
||||
|
|
|
@ -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: () => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue