Merge remote-tracking branch 'origin' into electron-19.x.y

This commit is contained in:
deepak1556 2022-07-09 01:21:17 +09:00
commit 11e80f7079
88 changed files with 968 additions and 2073 deletions

View file

@ -7,7 +7,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"June 2022\""
"value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"July 2022\""
},
{
"kind": 1,

View file

@ -1 +1 @@
2022-07-05T06:49:56.867Z
2022-07-08T16:20:45.398Z

View file

@ -452,8 +452,12 @@ async function getTranslations() {
}
const version = await getLatestStableVersion(updateUrl);
const languageIds = Object.keys(Languages);
return await Promise.all(languageIds.map(languageId => getNLS(resourceUrlTemplate, languageId, version)
const result = await Promise.allSettled(languageIds.map(languageId => getNLS(resourceUrlTemplate, languageId, version)
.catch(err => { console.warn(`Missing translation: ${languageId}@${version}`); return Promise.reject(err); })
.then(languageTranslations => ({ languageId, languageTranslations }))));
return result
.filter((r) => r.status === 'fulfilled')
.map(r => r.value);
}
async function main() {
const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]);

View file

@ -585,7 +585,8 @@ const Languages = {
};
type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } };
type Translations = { languageId: string; languageTranslations: LanguageTranslations }[];
type Translation = { languageId: string; languageTranslations: LanguageTranslations };
type Translations = Translation[];
async function getLatestStableVersion(updateUrl: string) {
const res = await fetch(`${updateUrl}/api/update/darwin/stable/latest`);
@ -643,10 +644,15 @@ async function getTranslations(): Promise<Translations> {
const version = await getLatestStableVersion(updateUrl);
const languageIds = Object.keys(Languages);
return await Promise.all(languageIds.map(
const result = await Promise.allSettled(languageIds.map(
languageId => getNLS(resourceUrlTemplate, languageId, version)
.catch(err => { console.warn(`Missing translation: ${languageId}@${version}`); return Promise.reject(err); })
.then(languageTranslations => ({ languageId, languageTranslations }))
));
return result
.filter((r): r is PromiseFulfilledResult<Translation> => r.status === 'fulfilled')
.map(r => r.value);
}
async function main() {

View file

@ -3,11 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace } from 'vscode';
import * as nls from 'vscode-nls';
import { Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace } from 'vscode';
import { Branch, Status } from './api/git';
import { Repository, Operation } from './repository';
import { dispose } from './util';
import { Branch } from './api/git';
const localize = nls.loadMessageBundle();
@ -16,7 +16,7 @@ interface ActionButtonState {
readonly isCommitInProgress: boolean;
readonly isMergeInProgress: boolean;
readonly isSyncInProgress: boolean;
readonly repositoryHasChanges: boolean;
readonly repositoryHasChangesToCommit: boolean;
}
export class ActionButtonCommand {
@ -40,7 +40,7 @@ export class ActionButtonCommand {
isCommitInProgress: false,
isMergeInProgress: false,
isSyncInProgress: false,
repositoryHasChanges: false
repositoryHasChangesToCommit: false
};
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
@ -48,11 +48,16 @@ export class ActionButtonCommand {
const root = Uri.file(repository.root);
this.disposables.push(workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('git.enableSmartCommit', root) ||
e.affectsConfiguration('git.smartCommitChanges', root) ||
e.affectsConfiguration('git.suggestSmartCommit', root)) {
this.onDidChangeSmartCommitSettings();
}
if (e.affectsConfiguration('git.branchProtection', root) ||
e.affectsConfiguration('git.branchProtectionPrompt', root) ||
e.affectsConfiguration('git.postCommitCommand', root) ||
e.affectsConfiguration('git.showActionButton', root)
) {
e.affectsConfiguration('git.showActionButton', root)) {
this._onDidChange.fire();
}
}));
@ -63,7 +68,7 @@ export class ActionButtonCommand {
let actionButton: SourceControlActionButton | undefined;
if (this.state.repositoryHasChanges) {
if (this.state.repositoryHasChangesToCommit) {
// Commit Changes (enabled)
actionButton = this.getCommitActionButton();
}
@ -160,7 +165,7 @@ export class ActionButtonCommand {
},
]
],
enabled: this.state.repositoryHasChanges && !this.state.isCommitInProgress && !this.state.isMergeInProgress
enabled: this.state.repositoryHasChangesToCommit && !this.state.isCommitInProgress && !this.state.isMergeInProgress
};
}
@ -223,19 +228,47 @@ export class ActionButtonCommand {
this.state = { ...this.state, isCommitInProgress, isSyncInProgress };
}
private onDidChangeSmartCommitSettings(): void {
this.state = {
...this.state,
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
};
}
private onDidRunGitStatus(): void {
this.state = {
...this.state,
HEAD: this.repository.HEAD,
isMergeInProgress:
this.repository.mergeGroup.resourceStates.length !== 0,
repositoryHasChanges:
this.repository.indexGroup.resourceStates.length !== 0 ||
this.repository.untrackedGroup.resourceStates.length !== 0 ||
this.repository.workingTreeGroup.resourceStates.length !== 0
isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0,
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
};
}
private repositoryHasChangesToCommit(): boolean {
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all');
const resources = [...this.repository.indexGroup.resourceStates];
if (
// Smart commit enabled (all)
(enableSmartCommit && smartCommitChanges === 'all') ||
// Smart commit disabled, smart suggestion enabled
(!enableSmartCommit && suggestSmartCommit)
) {
resources.push(...this.repository.workingTreeGroup.resourceStates);
}
// Smart commit enabled (tracked only)
if (enableSmartCommit && smartCommitChanges === 'tracked') {
resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED));
}
return resources.length !== 0;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}

View file

@ -5,14 +5,7 @@
import { ILogger } from 'vscode-markdown-languageservice';
class ConsoleLogger implements ILogger {
public verbose(title: string, message: string, data?: any): void {
this.appendLine(`[Verbose ${ConsoleLogger.now()}] ${title}: ${message}`);
if (data) {
this.appendLine(ConsoleLogger.data2String(data));
}
}
export class LogFunctionLogger implements ILogger {
private static now(): string {
const now = new Date();
@ -21,10 +14,6 @@ class ConsoleLogger implements ILogger {
+ ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0');
}
private appendLine(value: string): void {
console.log(value);
}
private static data2String(data: any): string {
if (data instanceof Error) {
if (typeof data.stack === 'string') {
@ -37,6 +26,21 @@ class ConsoleLogger implements ILogger {
}
return JSON.stringify(data, undefined, 2);
}
constructor(
private readonly _logFn: typeof console.log
) { }
public verbose(title: string, message: string, data?: any): void {
this.appendLine(`[Verbose ${LogFunctionLogger.now()}] ${title}: ${message}`);
if (data) {
this.appendLine(LogFunctionLogger.data2String(data));
}
}
private appendLine(value: string): void {
this._logFn(value);
}
}
export const consoleLogger = new ConsoleLogger();
export const consoleLogger = new LogFunctionLogger(console.log);

View file

@ -5,10 +5,10 @@
import { Connection, Emitter, Event, InitializeParams, InitializeResult, RequestType, TextDocuments } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { DocumentSymbol, Position, Range } from 'vscode-languageserver-types';
import * as lsp from 'vscode-languageserver-types';
import * as md from 'vscode-markdown-languageservice';
import { URI } from 'vscode-uri';
import { consoleLogger } from './logging';
import { LogFunctionLogger } from './logging';
const parseRequestType: RequestType<{ uri: string }, md.Token[], any> = new RequestType('markdown/parse');
@ -31,8 +31,7 @@ class TextDocumentToITextDocumentAdapter implements md.ITextDocument {
}
positionAt(offset: number): md.IPosition {
const pos = this._doc.positionAt(offset);
return md.makePosition(pos.line, pos.character);
return this._doc.positionAt(offset);
}
}
@ -44,6 +43,8 @@ export function startServer(connection: Connection) {
return {
capabilities: {
documentSymbolProvider: true,
foldingRangeProvider: true,
selectionRangeProvider: true,
}
};
});
@ -85,15 +86,38 @@ export function startServer(connection: Connection) {
}
};
const provider = md.createLanguageService(workspace, parser, consoleLogger);
const logger = new LogFunctionLogger(connection.console.log.bind(connection.console));
const provider = md.createLanguageService(workspace, parser, logger);
connection.onDocumentSymbol(async (documentSymbolParams, _token): Promise<DocumentSymbol[]> => {
connection.onDocumentSymbol(async (params, token): Promise<lsp.DocumentSymbol[]> => {
try {
const document = documents.get(documentSymbolParams.textDocument.uri) as TextDocument | undefined;
const document = documents.get(params.textDocument.uri);
if (document) {
const response = await provider.provideDocumentSymbols(new TextDocumentToITextDocumentAdapter(document));
// TODO: only required because extra methods returned on positions/ranges
return response.map(symbol => convertDocumentSymbol(symbol));
return await provider.provideDocumentSymbols(new TextDocumentToITextDocumentAdapter(document), token);
}
} catch (e) {
console.error(e.stack);
}
return [];
});
connection.onFoldingRanges(async (params, token): Promise<lsp.FoldingRange[]> => {
try {
const document = documents.get(params.textDocument.uri);
if (document) {
return await provider.provideFoldingRanges(new TextDocumentToITextDocumentAdapter(document), token);
}
} catch (e) {
console.error(e.stack);
}
return [];
});
connection.onSelectionRanges(async (params, token): Promise<lsp.SelectionRange[] | undefined> => {
try {
const document = documents.get(params.textDocument.uri);
if (document) {
return await provider.provideSelectionRanges(new TextDocumentToITextDocumentAdapter(document), params.positions, token);
}
} catch (e) {
console.error(e.stack);
@ -103,30 +127,3 @@ export function startServer(connection: Connection) {
connection.listen();
}
function convertDocumentSymbol(sym: DocumentSymbol): DocumentSymbol {
return {
kind: sym.kind,
name: sym.name,
range: convertRange(sym.range),
selectionRange: convertRange(sym.selectionRange),
children: sym.children?.map(convertDocumentSymbol),
detail: sym.detail,
tags: sym.tags,
};
}
function convertRange(range: Range): Range {
return {
start: convertPosition(range.start),
end: convertPosition(range.end),
};
}
function convertPosition(start: Position): Position {
return {
character: start.character,
line: start.line,
};
}

View file

@ -43,8 +43,8 @@ vscode-languageserver@^8.0.2-next.4:
vscode-languageserver-protocol "3.17.2-next.6"
vscode-markdown-languageservice@mjbvz/vscode-markdown-languageservice:
version "1.0.0"
resolved "https://codeload.github.com/mjbvz/vscode-markdown-languageservice/tar.gz/e410b5df64659fbc186cf0a7a7c882c451e07b8b"
version "0.0.0-alpha.1"
resolved "https://codeload.github.com/mjbvz/vscode-markdown-languageservice/tar.gz/e1a0e00bf6a99cc543da64964cc0995537647d15"
dependencies:
vscode-languageserver-types "^3.17.1"
vscode-uri "^3.0.3"

View file

@ -30,7 +30,7 @@ export function activate(context: vscode.ExtensionContext) {
}
async function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise<void> {
const clientMain = vscode.extensions.getExtension('vscode.css-language-features')?.packageJSON?.main || '';
const clientMain = vscode.extensions.getExtension('vscode.markdown-language-features')?.packageJSON?.main || '';
const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/main`;
const serverModule = context.asAbsolutePath(serverMain);

View file

@ -13,11 +13,9 @@ import { MdLinkProvider, registerDocumentLinkSupport } from './languageFeatures/
import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbols';
import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor';
import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences';
import { registerFoldingSupport } from './languageFeatures/folding';
import { registerPathCompletionSupport } from './languageFeatures/pathCompletions';
import { MdReferencesProvider, registerReferencesSupport } from './languageFeatures/references';
import { registerRenameSupport } from './languageFeatures/rename';
import { registerSmartSelectSupport } from './languageFeatures/smartSelect';
import { registerWorkspaceSymbolSupport } from './languageFeatures/workspaceSymbols';
import { ILogger } from './logging';
import { IMdParser, MarkdownItEngine, MdParsingProvider } from './markdownEngine';
@ -81,12 +79,10 @@ function registerMarkdownLanguageFeatures(
registerDocumentLinkSupport(selector, linkProvider),
registerDropIntoEditorSupport(selector),
registerFindFileReferenceSupport(commandManager, referencesProvider),
registerFoldingSupport(selector, parser, tocProvider),
registerPasteSupport(selector),
registerPathCompletionSupport(selector, workspace, parser, linkProvider),
registerReferencesSupport(selector, referencesProvider),
registerRenameSupport(selector, workspace, referencesProvider, parser.slugifier),
registerSmartSelectSupport(selector, parser, tocProvider),
registerWorkspaceSymbolSupport(workspace, symbolProvider),
);
}

View file

@ -1,123 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { IMdParser } from '../markdownEngine';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { getLine, ITextDocument } from '../types/textDocument';
import { isEmptyOrWhitespace } from '../util/string';
const rangeLimit = 5000;
interface MarkdownItTokenWithMap extends Token {
map: [number, number];
}
export class MdFoldingProvider implements vscode.FoldingRangeProvider {
constructor(
private readonly parser: IMdParser,
private readonly tocProvide: MdTableOfContentsProvider,
) { }
public async provideFoldingRanges(
document: ITextDocument,
_: vscode.FoldingContext,
_token: vscode.CancellationToken
): Promise<vscode.FoldingRange[]> {
const foldables = await Promise.all([
this.getRegions(document),
this.getHeaderFoldingRanges(document),
this.getBlockFoldingRanges(document)
]);
return foldables.flat().slice(0, rangeLimit);
}
private async getRegions(document: ITextDocument): Promise<vscode.FoldingRange[]> {
const tokens = await this.parser.tokenize(document);
const regionMarkers = tokens.filter(isRegionMarker)
.map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) }));
const nestingStack: { line: number; isStart: boolean }[] = [];
return regionMarkers
.map(marker => {
if (marker.isStart) {
nestingStack.push(marker);
} else if (nestingStack.length && nestingStack[nestingStack.length - 1].isStart) {
return new vscode.FoldingRange(nestingStack.pop()!.line, marker.line, vscode.FoldingRangeKind.Region);
} else {
// noop: invalid nesting (i.e. [end, start] or [start, end, end])
}
return null;
})
.filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region);
}
private async getHeaderFoldingRanges(document: ITextDocument): Promise<vscode.FoldingRange[]> {
const toc = await this.tocProvide.getForDocument(document);
return toc.entries.map(entry => {
let endLine = entry.sectionLocation.range.end.line;
if (isEmptyOrWhitespace(getLine(document, endLine)) && endLine >= entry.line + 1) {
endLine = endLine - 1;
}
return new vscode.FoldingRange(entry.line, endLine);
});
}
private async getBlockFoldingRanges(document: ITextDocument): Promise<vscode.FoldingRange[]> {
const tokens = await this.parser.tokenize(document);
const multiLineListItems = tokens.filter(isFoldableToken);
return multiLineListItems.map(listItem => {
const start = listItem.map[0];
let end = listItem.map[1] - 1;
if (isEmptyOrWhitespace(getLine(document, end)) && end >= start + 1) {
end = end - 1;
}
return new vscode.FoldingRange(start, end, this.getFoldingRangeKind(listItem));
});
}
private getFoldingRangeKind(listItem: Token): vscode.FoldingRangeKind | undefined {
return listItem.type === 'html_block' && listItem.content.startsWith('<!--')
? vscode.FoldingRangeKind.Comment
: undefined;
}
}
const isStartRegion = (t: string) => /^\s*<!--\s*#?region\b.*-->/.test(t);
const isEndRegion = (t: string) => /^\s*<!--\s*#?endregion\b.*-->/.test(t);
const isRegionMarker = (token: Token): token is MarkdownItTokenWithMap =>
!!token.map && token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content));
const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => {
if (!token.map) {
return false;
}
switch (token.type) {
case 'fence':
case 'list_item_open':
return token.map[1] > token.map[0];
case 'html_block':
if (isRegionMarker(token)) {
return false;
}
return token.map[1] > token.map[0] + 1;
default:
return false;
}
};
export function registerFoldingSupport(
selector: vscode.DocumentSelector,
parser: IMdParser,
tocProvider: MdTableOfContentsProvider,
): vscode.Disposable {
return vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(parser, tocProvider));
}

View file

@ -1,259 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { IMdParser } from '../markdownEngine';
import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents';
import { getLine, ITextDocument } from '../types/textDocument';
import { isEmptyOrWhitespace } from '../util/string';
interface MarkdownItTokenWithMap extends Token {
map: [number, number];
}
export class MdSmartSelect implements vscode.SelectionRangeProvider {
constructor(
private readonly parser: IMdParser,
private readonly tocProvider: MdTableOfContentsProvider,
) { }
public async provideSelectionRanges(document: ITextDocument, positions: vscode.Position[], _token: vscode.CancellationToken): Promise<vscode.SelectionRange[] | undefined> {
const promises = await Promise.all(positions.map((position) => {
return this.provideSelectionRange(document, position, _token);
}));
return promises.filter(item => item !== undefined) as vscode.SelectionRange[];
}
private async provideSelectionRange(document: ITextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.SelectionRange | undefined> {
const headerRange = await this.getHeaderSelectionRange(document, position);
const blockRange = await this.getBlockSelectionRange(document, position, headerRange);
const inlineRange = await this.getInlineSelectionRange(document, position, blockRange);
return inlineRange || blockRange || headerRange;
}
private async getInlineSelectionRange(document: ITextDocument, position: vscode.Position, blockRange?: vscode.SelectionRange): Promise<vscode.SelectionRange | undefined> {
return createInlineRange(document, position, blockRange);
}
private async getBlockSelectionRange(document: ITextDocument, position: vscode.Position, headerRange?: vscode.SelectionRange): Promise<vscode.SelectionRange | undefined> {
const tokens = await this.parser.tokenize(document);
const blockTokens = getBlockTokensForPosition(tokens, position, headerRange);
if (blockTokens.length === 0) {
return undefined;
}
let currentRange: vscode.SelectionRange | undefined = headerRange ? headerRange : createBlockRange(blockTokens.shift()!, document, position.line);
for (let i = 0; i < blockTokens.length; i++) {
currentRange = createBlockRange(blockTokens[i], document, position.line, currentRange);
}
return currentRange;
}
private async getHeaderSelectionRange(document: ITextDocument, position: vscode.Position): Promise<vscode.SelectionRange | undefined> {
const toc = await this.tocProvider.getForDocument(document);
const headerInfo = getHeadersForPosition(toc.entries, position);
const headers = headerInfo.headers;
let currentRange: vscode.SelectionRange | undefined;
for (let i = 0; i < headers.length; i++) {
currentRange = createHeaderRange(headers[i], i === headers.length - 1, headerInfo.headerOnThisLine, currentRange, getFirstChildHeader(document, headers[i], toc.entries));
}
return currentRange;
}
}
function getHeadersForPosition(toc: readonly TocEntry[], position: vscode.Position): { headers: TocEntry[]; headerOnThisLine: boolean } {
const enclosingHeaders = toc.filter(header => header.sectionLocation.range.start.line <= position.line && header.sectionLocation.range.end.line >= position.line);
const sortedHeaders = enclosingHeaders.sort((header1, header2) => (header1.line - position.line) - (header2.line - position.line));
const onThisLine = toc.find(header => header.line === position.line) !== undefined;
return {
headers: sortedHeaders,
headerOnThisLine: onThisLine
};
}
function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean, onHeaderLine: boolean, parent?: vscode.SelectionRange, startOfChildRange?: vscode.Position): vscode.SelectionRange | undefined {
const range = header.sectionLocation.range;
const contentRange = new vscode.Range(range.start.translate(1), range.end);
if (onHeaderLine && isClosestHeaderToPosition && startOfChildRange) {
// selection was made on this header line, so select header and its content until the start of its first child
// then all of its content
return new vscode.SelectionRange(range.with(undefined, startOfChildRange), new vscode.SelectionRange(range, parent));
} else if (onHeaderLine && isClosestHeaderToPosition) {
// selection was made on this header line and no children so expand to all of its content
return new vscode.SelectionRange(range, parent);
} else if (isClosestHeaderToPosition && startOfChildRange) {
// selection was made within content and has child so select content
// of this header then all content then header
return new vscode.SelectionRange(contentRange.with(undefined, startOfChildRange), new vscode.SelectionRange(contentRange, (new vscode.SelectionRange(range, parent))));
} else {
// not on this header line so select content then header
return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(range, parent));
}
}
function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): MarkdownItTokenWithMap[] {
const enclosingTokens = tokens.filter((token): token is MarkdownItTokenWithMap => !!token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token));
if (enclosingTokens.length === 0) {
return [];
}
const sortedTokens = enclosingTokens.sort((token1, token2) => (token2.map[1] - token2.map[0]) - (token1.map[1] - token1.map[0]));
return sortedTokens;
}
function createBlockRange(block: MarkdownItTokenWithMap, document: ITextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
if (block.type === 'fence') {
return createFencedRange(block, cursorLine, document, parent);
} else {
let startLine = isEmptyOrWhitespace(getLine(document, block.map[0])) ? block.map[0] + 1 : block.map[0];
let endLine = startLine === block.map[1] ? block.map[1] : block.map[1] - 1;
if (block.type === 'paragraph_open' && block.map[1] - block.map[0] === 2) {
startLine = endLine = cursorLine;
} else if (isList(block) && isEmptyOrWhitespace(getLine(document, endLine))) {
endLine = endLine - 1;
}
const range = new vscode.Range(startLine, 0, endLine, getLine(document, endLine).length);
if (parent?.range.contains(range) && !parent.range.isEqual(range)) {
return new vscode.SelectionRange(range, parent);
} else if (parent?.range.isEqual(range)) {
return parent;
} else {
return new vscode.SelectionRange(range);
}
}
}
function createInlineRange(document: ITextDocument, cursorPosition: vscode.Position, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
const lineText = getLine(document, cursorPosition.line);
const boldSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, parent);
const italicSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, parent);
let comboSelection: vscode.SelectionRange | undefined;
if (boldSelection && italicSelection && !boldSelection.range.isEqual(italicSelection.range)) {
if (boldSelection.range.contains(italicSelection.range)) {
comboSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, boldSelection);
} else if (italicSelection.range.contains(boldSelection.range)) {
comboSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, italicSelection);
}
}
const linkSelection = createLinkRange(lineText, cursorPosition.character, cursorPosition.line, comboSelection || boldSelection || italicSelection || parent);
const inlineCodeBlockSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, false, linkSelection || parent);
return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection;
}
function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: ITextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
const startLine = token.map[0];
const endLine = token.map[1] - 1;
const onFenceLine = cursorLine === startLine || cursorLine === endLine;
const fenceRange = new vscode.Range(startLine, 0, endLine, getLine(document, endLine).length);
const contentRange = endLine - startLine > 2 && !onFenceLine ? new vscode.Range(startLine + 1, 0, endLine - 1, getLine(document, endLine - 1).length) : undefined;
if (contentRange) {
return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(fenceRange, parent));
} else {
if (parent?.range.isEqual(fenceRange)) {
return parent;
} else {
return new vscode.SelectionRange(fenceRange, parent);
}
}
}
function createBoldRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
const regex = /\*\*([^*]+\*?[^*]+\*?[^*]+)\*\*/gim;
const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar);
if (matches.length) {
// should only be one match, so select first and index 0 contains the entire match
const bold = matches[0][0];
const startIndex = lineText.indexOf(bold);
const cursorOnStars = cursorChar === startIndex || cursorChar === startIndex + 1 || cursorChar === startIndex + bold.length || cursorChar === startIndex + bold.length - 1;
const contentAndStars = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + bold.length), parent);
const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 2, cursorLine, startIndex + bold.length - 2), contentAndStars);
return cursorOnStars ? contentAndStars : content;
}
return undefined;
}
function createOtherInlineRange(lineText: string, cursorChar: number, cursorLine: number, isItalic: boolean, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
const italicRegexes = [/(?:[^*]+)(\*([^*]+)(?:\*\*[^*]*\*\*)*([^*]+)\*)(?:[^*]+)/g, /^(?:[^*]*)(\*([^*]+)(?:\*\*[^*]*\*\*)*([^*]+)\*)(?:[^*]*)$/g];
let matches = [];
if (isItalic) {
matches = [...lineText.matchAll(italicRegexes[0])].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar);
if (!matches.length) {
matches = [...lineText.matchAll(italicRegexes[1])].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar);
}
} else {
matches = [...lineText.matchAll(/\`[^\`]*\`/g)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar);
}
if (matches.length) {
// should only be one match, so select first and select group 1 for italics because that contains just the italic section
// doesn't include the leading and trailing characters which are guaranteed to not be * so as not to be confused with bold
const match = isItalic ? matches[0][1] : matches[0][0];
const startIndex = lineText.indexOf(match);
const cursorOnType = cursorChar === startIndex || cursorChar === startIndex + match.length;
const contentAndType = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + match.length), parent);
const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 1, cursorLine, startIndex + match.length - 1), contentAndType);
return cursorOnType ? contentAndType : content;
}
return undefined;
}
function createLinkRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
const regex = /(\[[^\(\)]*\])(\([^\[\]]*\))/g;
const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length > cursorChar);
if (matches.length) {
// should only be one match, so select first and index 0 contains the entire match, so match = [text](url)
const link = matches[0][0];
const linkRange = new vscode.SelectionRange(new vscode.Range(cursorLine, lineText.indexOf(link), cursorLine, lineText.indexOf(link) + link.length), parent);
const linkText = matches[0][1];
const url = matches[0][2];
// determine if cursor is within [text] or (url) in order to know which should be selected
const nearestType = cursorChar >= lineText.indexOf(linkText) && cursorChar < lineText.indexOf(linkText) + linkText.length ? linkText : url;
const indexOfType = lineText.indexOf(nearestType);
// determine if cursor is on a bracket or paren and if so, return the [content] or (content), skipping over the content range
const cursorOnType = cursorChar === indexOfType || cursorChar === indexOfType + nearestType.length;
const contentAndNearestType = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType, cursorLine, indexOfType + nearestType.length), linkRange);
const content = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType + 1, cursorLine, indexOfType + nearestType.length - 1), contentAndNearestType);
return cursorOnType ? contentAndNearestType : content;
}
return undefined;
}
function isList(token: Token): boolean {
return token.type ? ['ordered_list_open', 'list_item_open', 'bullet_list_open'].includes(token.type) : false;
}
function isBlockElement(token: Token): boolean {
return !['list_item_close', 'paragraph_close', 'bullet_list_close', 'inline', 'heading_close', 'heading_open'].includes(token.type);
}
function getFirstChildHeader(document: ITextDocument, header?: TocEntry, toc?: readonly TocEntry[]): vscode.Position | undefined {
let childRange: vscode.Position | undefined;
if (header && toc) {
const children = toc.filter(t => header.sectionLocation.range.contains(t.sectionLocation.range) && t.sectionLocation.range.start.line > header.sectionLocation.range.start.line).sort((t1, t2) => t1.line - t2.line);
if (children.length > 0) {
childRange = children[0].sectionLocation.range.start;
const lineText = getLine(document, childRange.line - 1);
return childRange ? childRange.translate(-1, lineText.length) : undefined;
}
}
return undefined;
}
export function registerSmartSelectSupport(
selector: vscode.DocumentSelector,
parser: IMdParser,
tocProvider: MdTableOfContentsProvider,
): vscode.Disposable {
return vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(parser, tocProvider));
}

View file

@ -1,230 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdFoldingProvider } from '../languageFeatures/folding';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { joinLines, withStore } from './util';
const testFileName = vscode.Uri.file('test.md');
async function getFoldsForDocument(store: DisposableStore, contents: string) {
const doc = new InMemoryDocument(testFileName, contents);
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const engine = createNewMarkdownEngine();
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const provider = new MdFoldingProvider(engine, tocProvider);
return provider.provideFoldingRanges(doc, {}, noopToken);
}
suite('markdown.FoldingProvider', () => {
test('Should not return anything for empty document', withStore(async (store) => {
const folds = await getFoldsForDocument(store, ``);
assert.strictEqual(folds.length, 0);
}));
test('Should not return anything for document without headers', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`**b** afas`,
`a#b`,
`a`,
));
assert.strictEqual(folds.length, 0);
}));
test('Should fold from header to end of document', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`# b`,
`c`,
`d`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
}));
test('Should leave single newline before next header', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
``,
`# a`,
`x`,
``,
`# b`,
`y`,
));
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 2);
}));
test('Should collapse multiple newlines to single newline before next header', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
``,
`# a`,
`x`,
``,
``,
``,
`# b`,
`y`
));
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 4);
}));
test('Should not collapse if there is no newline before next header', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
``,
`# a`,
`x`,
`# b`,
`y`,
));
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 2);
}));
test('Should fold nested <!-- #region --> markers', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`<!-- #region -->`,
`b`,
`<!-- #region hello!-->`,
`b.a`,
`<!-- #endregion -->`,
`b`,
`<!-- #region: foo! -->`,
`b.b`,
`<!-- #endregion: foo -->`,
`b`,
`<!-- #endregion -->`,
`a`,
));
assert.strictEqual(folds.length, 3);
const [outer, first, second] = folds.sort((a, b) => a.start - b.start);
assert.strictEqual(outer.start, 1);
assert.strictEqual(outer.end, 11);
assert.strictEqual(first.start, 3);
assert.strictEqual(first.end, 5);
assert.strictEqual(second.start, 7);
assert.strictEqual(second.end, 9);
}));
test('Should fold from list to end of document', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`- b`,
`c`,
`d`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
}));
test('lists folds should span multiple lines of content', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`- This list item\n spans multiple\n lines.`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
}));
test('List should leave single blankline before new element', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`- a`,
`a`,
``,
``,
`b`
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 0);
assert.strictEqual(firstFold.end, 2);
}));
test('Should fold fenced code blocks', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`~~~ts`,
`a`,
`~~~`,
`b`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 0);
assert.strictEqual(firstFold.end, 2);
}));
test('Should fold fenced code blocks with yaml front matter', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`---`,
`title: bla`,
`---`,
``,
`~~~ts`,
`a`,
`~~~`,
``,
`a`,
`a`,
`b`,
`a`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 4);
assert.strictEqual(firstFold.end, 6);
}));
test('Should fold html blocks', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`x`,
`<div>`,
` fa`,
`</div>`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
}));
test('Should fold html block comments', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`x`,
`<!--`,
`fa`,
`-->`
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
assert.strictEqual(firstFold.kind, vscode.FoldingRangeKind.Comment);
}));
});

View file

@ -1,731 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as vscode from 'vscode';
import { MdSmartSelect } from '../languageFeatures/smartSelect';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { CURSOR, getCursorPositions, joinLines } from './util';
const testFileName = vscode.Uri.file('test.md');
suite('markdown.SmartSelect', () => {
test('Smart select single word', async () => {
const ranges = await getSelectionRangesForDocument(`Hel${CURSOR}lo`);
assertNestedLineNumbersEqual(ranges![0], [0, 0]);
});
test('Smart select multi-line paragraph', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. `,
`For example, the[node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter]`,
`(https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).`
));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select paragraph', async () => {
const ranges = await getSelectionRangesForDocument(`Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).`);
assertNestedLineNumbersEqual(ranges![0], [0, 0]);
});
test('Smart select html block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`<p align="center">`,
`${CURSOR}<img alt="VS Code in action" src="https://user-images.githubusercontent.com/1487073/58344409-70473b80-7e0a-11e9-8570-b2efc6f8fa44.png">`,
`</p>`));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select header on header line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# Header${CURSOR}`,
`Hello`));
assertNestedLineNumbersEqual(ranges![0], [0, 1]);
});
test('Smart select single word w grandparent header on text line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`## ParentHeader`,
`# Header`,
`${CURSOR}Hello`
));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 2]);
});
test('Smart select html block w parent header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# Header`,
`${CURSOR}<p align="center">`,
`<img alt="VS Code in action" src="https://user-images.githubusercontent.com/1487073/58344409-70473b80-7e0a-11e9-8570-b2efc6f8fa44.png">`,
`</p>`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 3], [0, 3]);
});
test('Smart select fenced code block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`~~~`,
`a${CURSOR}`,
`~~~`));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- item 1`,
`- ${CURSOR}item 2`,
`- item 3`,
`- item 4`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [0, 3]);
});
test('Smart select list with fenced code block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- item 1`,
`- ~~~`,
` ${CURSOR}a`,
` ~~~`,
`- item 3`,
`- item 4`));
assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 5]);
});
test('Smart select multi cursor', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- ${CURSOR}item 1`,
`- ~~~`,
` a`,
` ~~~`,
`- ${CURSOR}item 3`,
`- item 4`));
assertNestedLineNumbersEqual(ranges![0], [0, 0], [0, 5]);
assertNestedLineNumbersEqual(ranges![1], [4, 4], [0, 5]);
});
test('Smart select nested block quotes', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`> item 1`,
`> item 2`,
`>> ${CURSOR}item 3`,
`>> item 4`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [0, 3]);
});
test('Smart select multi nested block quotes', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`> item 1`,
`>> item 2`,
`>>> ${CURSOR}item 3`,
`>>>> item 4`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [1, 3], [0, 3]);
});
test('Smart select subheader content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
`content 1`,
`## sub header 1`,
`${CURSOR}content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [2, 3], [1, 3], [0, 3]);
});
test('Smart select subheader line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
`content 1`,
`## sub header 1${CURSOR}`,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [2, 3], [1, 3], [0, 3]);
});
test('Smart select blank line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
`content 1`,
`${CURSOR} `,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 3]);
});
test('Smart select line between paragraphs', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`paragraph 1`,
`${CURSOR}`,
`paragraph 2`));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select empty document', async () => {
const ranges = await getSelectionRangesForDocument(``, [new vscode.Position(0, 0)]);
assert.strictEqual(ranges!.length, 0);
});
test('Smart select fenced code block then list then subheader content then subheader then header content then header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
/* 00 */ `# main header 1`,
/* 01 */ `content 1`,
/* 02 */ `## sub header 1`,
/* 03 */ `- item 1`,
/* 04 */ `- ~~~`,
/* 05 */ ` ${CURSOR}a`,
/* 06 */ ` ~~~`,
/* 07 */ `- item 3`,
/* 08 */ `- item 4`,
/* 09 */ ``,
/* 10 */ `more content`,
/* 11 */ `# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [4, 6], [3, 8], [3, 10], [2, 10], [1, 10], [0, 10]);
});
test('Smart select list with one element without selecting child subheader', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
/* 00 */ `# main header 1`,
/* 01 */ ``,
/* 02 */ `- list ${CURSOR}`,
/* 03 */ ``,
/* 04 */ `## sub header`,
/* 05 */ ``,
/* 06 */ `content 2`,
/* 07 */ `# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 3], [1, 6], [0, 6]);
});
test('Smart select content under header then subheaders and their content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main ${CURSOR}header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
``,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [0, 3], [0, 6]);
});
test('Smart select last blockquote element under header then subheaders and their content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> ${CURSOR}block`,
``,
`paragraph`,
`## sub header`,
``,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [5, 5], [4, 5], [2, 5], [1, 7], [1, 10], [0, 10]);
});
test('Smart select content of subheader then subheader then content of main header then main header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`paragraph`,
`## sub header`,
``,
``,
`${CURSOR}`,
``,
`### main header 2`,
`- content 2`,
`- content 2`,
`- content 2`,
`content 2`));
assertNestedLineNumbersEqual(ranges![0], [11, 11], [9, 12], [9, 17], [8, 17], [1, 17], [0, 17]);
});
test('Smart select last line content of subheader then subheader then content of main header then main header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`paragraph`,
`## sub header`,
``,
``,
``,
``,
`### main header 2`,
`- content 2`,
`- content 2`,
`- content 2`,
`- ${CURSOR}content 2`));
assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]);
});
test('Smart select last line content after content of subheader then subheader then content of main header then main header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`paragraph`,
`## sub header`,
``,
``,
``,
``,
`### main header 2`,
`- content 2`,
`- content 2`,
`- content 2`,
`- content 2${CURSOR}`));
assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]);
});
test('Smart select fenced code block then list then rest of content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`- paragraph`,
`- ~~~`,
` my`,
` ${CURSOR}code`,
` goes here`,
` ~~~`,
`- content`,
`- content 2`,
`- content 2`,
`- content 2`,
`- content 2`));
assertNestedLineNumbersEqual(ranges![0], [9, 11], [8, 12], [8, 12], [7, 17], [1, 17], [0, 17]);
});
test('Smart select fenced code block then list then rest of content on fenced line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`- paragraph`,
`- ~~~${CURSOR}`,
` my`,
` code`,
` goes here`,
` ~~~`,
`- content`,
`- content 2`,
`- content 2`,
`- content 2`,
`- content 2`));
assertNestedLineNumbersEqual(ranges![0], [8, 12], [7, 17], [1, 17], [0, 17]);
});
test('Smart select without multiple ranges', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
``,
`- ${CURSOR}paragraph`,
`- content`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]);
});
test('Smart select on second level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level 2`,
` * level 1`,
` * level ${CURSOR}1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]);
});
test('Smart select on third level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level ${CURSOR}2`,
` * level 2`,
` * level 1`,
` * level 1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]);
});
test('Smart select level 2 then level 1', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 1`,
` * level ${CURSOR}2`,
` * level 2`,
`* level 1`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]);
});
test('Smart select last list item', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- level 1`,
`- level 2`,
`- level 2`,
`- level ${CURSOR}1`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [0, 3]);
});
test('Smart select without multiple ranges', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
``,
`- ${CURSOR}paragraph`,
`- content`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]);
});
test('Smart select on second level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level 2`,
` * level 1`,
` * level ${CURSOR}1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]);
});
test('Smart select on third level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level ${CURSOR}2`,
` * level 2`,
` * level 1`,
` * level 1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]);
});
test('Smart select level 2 then level 1', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 1`,
` * level ${CURSOR}2`,
` * level 2`,
`* level 1`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]);
});
test('Smart select bold', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`stuff here **new${CURSOR}item** and here`
));
assertNestedRangesEqual(ranges![0], [0, 13, 0, 30], [0, 11, 0, 32], [0, 0, 0, 41]);
});
test('Smart select link', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`stuff here [text](https${CURSOR}://google.com) and here`
));
assertNestedRangesEqual(ranges![0], [0, 18, 0, 46], [0, 17, 0, 47], [0, 11, 0, 47], [0, 0, 0, 56]);
});
test('Smart select brackets', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`stuff here [te${CURSOR}xt](https://google.com) and here`
));
assertNestedRangesEqual(ranges![0], [0, 12, 0, 26], [0, 11, 0, 27], [0, 11, 0, 47], [0, 0, 0, 56]);
});
test('Smart select brackets under header in list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
`- list`,
`- stuff here [te${CURSOR}xt](https://google.com) and here`,
`- list`
));
assertNestedRangesEqual(ranges![0], [6, 14, 6, 28], [6, 13, 6, 29], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]);
});
test('Smart select link under header in list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
`- list`,
`- stuff here [text](${CURSOR}https://google.com) and here`,
`- list`
));
assertNestedRangesEqual(ranges![0], [6, 20, 6, 48], [6, 19, 6, 49], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]);
});
test('Smart select bold within list where multiple bold elements exists', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
`- list`,
`- stuff here [text] **${CURSOR}items in here** and **here**`,
`- list`
));
assertNestedRangesEqual(ranges![0], [6, 22, 6, 45], [6, 20, 6, 47], [6, 0, 6, 60], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]);
});
test('Smart select link in paragraph with multiple links', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`This[extension](https://marketplace.visualstudio.com/items?itemName=meganrogge.template-string-converter) addresses this [requ${CURSOR}est](https://github.com/microsoft/vscode/issues/56704) to convert Javascript/Typescript quotes to backticks when has been entered within a string.`
));
assertNestedRangesEqual(ranges![0], [0, 123, 0, 140], [0, 122, 0, 141], [0, 122, 0, 191], [0, 0, 0, 283]);
});
test('Smart select bold link', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`**[extens${CURSOR}ion](https://google.com)**`
));
assertNestedRangesEqual(ranges![0], [0, 3, 0, 22], [0, 2, 0, 23], [0, 2, 0, 43], [0, 2, 0, 43], [0, 0, 0, 45], [0, 0, 0, 45]);
});
test('Smart select inline code block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`[\`code ${CURSOR} link\`]`
));
assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 0, 0, 24]);
});
test('Smart select link with inline code block text', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`[\`code ${CURSOR} link\`](http://example.com)`
));
assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 1, 0, 23], [0, 0, 0, 24], [0, 0, 0, 44], [0, 0, 0, 44]);
});
test('Smart select italic', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`*some nice ${CURSOR}text*`
));
assertNestedRangesEqual(ranges![0], [0, 1, 0, 25], [0, 0, 0, 26], [0, 0, 0, 26]);
});
test('Smart select italic link', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`*[extens${CURSOR}ion](https://google.com)*`
));
assertNestedRangesEqual(ranges![0], [0, 2, 0, 21], [0, 1, 0, 22], [0, 1, 0, 42], [0, 1, 0, 42], [0, 0, 0, 43], [0, 0, 0, 43]);
});
test('Smart select italic on end', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`*word1 word2 word3${CURSOR}*`
));
assertNestedRangesEqual(ranges![0], [0, 1, 0, 28], [0, 0, 0, 29], [0, 0, 0, 29]);
});
test('Smart select italic then bold', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`outer text **bold words *italic ${CURSOR} words* bold words** outer text`
));
assertNestedRangesEqual(ranges![0], [0, 25, 0, 48], [0, 24, 0, 49], [0, 13, 0, 60], [0, 11, 0, 62], [0, 0, 0, 73]);
});
test('Smart select bold then italic', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`outer text *italic words **bold ${CURSOR} words** italic words* outer text`
));
assertNestedRangesEqual(ranges![0], [0, 27, 0, 48], [0, 25, 0, 50], [0, 12, 0, 63], [0, 11, 0, 64], [0, 0, 0, 75]);
});
test('Third level header from release notes', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`---`,
`Order: 60`,
`TOCTitle: October 2020`,
`PageTitle: Visual Studio Code October 2020`,
`MetaDescription: Learn what is new in the Visual Studio Code October 2020 Release (1.51)`,
`MetaSocialImage: 1_51/release-highlights.png`,
`Date: 2020-11-6`,
`DownloadVersion: 1.51.1`,
`---`,
`# October 2020 (version 1.51)`,
``,
`**Update 1.51.1**: The update addresses these [issues](https://github.com/microsoft/vscode/issues?q=is%3Aissue+milestone%3A%22October+2020+Recovery%22+is%3Aclosed+).`,
``,
`<!-- DOWNLOAD_LINKS_PLACEHOLDER -->`,
``,
`---`,
``,
`Welcome to the October 2020 release of Visual Studio Code. As announced in the [October iteration plan](https://github.com/microsoft/vscode/issues/108473), we focused on housekeeping GitHub issues and pull requests as documented in our issue grooming guide.`,
``,
`We also worked with our partners at GitHub on GitHub Codespaces, which ended up being more involved than originally anticipated. To that end, we'll continue working on housekeeping for part of the November iteration.`,
``,
`During this housekeeping milestone, we also addressed several feature requests and community [pull requests](#thank-you). Read on to learn about new features and settings.`,
``,
`## Workbench`,
``,
`### More prominent pinned tabs`,
``,
`${CURSOR}Pinned tabs will now always show their pin icon, even while inactive, to make them easier to identify. If an editor is both pinned and contains unsaved changes, the icon reflects both states.`,
``,
`![Inactive pinned tabs showing pin icons](images/1_51/pinned-tabs.png)`
)
);
assertNestedRangesEqual(ranges![0], [27, 0, 27, 201], [26, 0, 29, 70], [25, 0, 29, 70], [24, 0, 29, 70], [23, 0, 29, 70], [10, 0, 29, 70], [9, 0, 29, 70]);
});
});
function assertNestedLineNumbersEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number][]) {
const lineage = getLineage(range);
assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`);
for (let i = 0; i < lineage.length; i++) {
assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][1], `parent at a depth of ${i}`);
}
}
function assertNestedRangesEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number, number, number][]) {
const lineage = getLineage(range);
assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`);
for (let i = 0; i < lineage.length; i++) {
assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][2], `parent at a depth of ${i}`);
assert(lineage[i].range.start.character === expectedRanges[i][1], `parent at a depth of ${i} on start char`);
assert(lineage[i].range.end.character === expectedRanges[i][3], `parent at a depth of ${i} on end char`);
}
}
function getLineage(range: vscode.SelectionRange): vscode.SelectionRange[] {
const result: vscode.SelectionRange[] = [];
let currentRange: vscode.SelectionRange | undefined = range;
while (currentRange) {
result.push(currentRange);
currentRange = currentRange.parent;
}
return result;
}
function getValues(ranges: vscode.SelectionRange[]): string[] {
return ranges.map(range => {
return range.range.start.line + ' ' + range.range.start.character + ' ' + range.range.end.line + ' ' + range.range.end.character;
});
}
function assertLineNumbersEqual(selectionRange: vscode.SelectionRange, startLine: number, endLine: number, message: string) {
assert.strictEqual(selectionRange.range.start.line, startLine, `failed on start line ${message}`);
assert.strictEqual(selectionRange.range.end.line, endLine, `failed on end line ${message}`);
}
function getSelectionRangesForDocument(contents: string, pos?: vscode.Position[]): Promise<vscode.SelectionRange[] | undefined> {
const doc = new InMemoryDocument(testFileName, contents);
const workspace = new InMemoryMdWorkspace([doc]);
const engine = createNewMarkdownEngine();
const provider = new MdSmartSelect(engine, new MdTableOfContentsProvider(engine, workspace, nulLogger));
const positions = pos ? pos : getCursorPositions(contents, doc);
return provider.provideSelectionRanges(doc, positions, new vscode.CancellationTokenSource().token);
}

View file

@ -1157,10 +1157,8 @@ suite('vscode API - workspace', () => {
assert.ok(edt === vscode.window.activeTextEditor);
const we = new vscode.WorkspaceEdit();
we.set(document.uri, [{ range: new vscode.Range(0, 0, 0, 0), newText: '', newText2: new vscode.SnippetString('${1:foo}${2:bar}') }]);
we.replace(document.uri, new vscode.Range(0, 0, 0, 0), new vscode.SnippetString('${1:foo}${2:bar}'));
const success = await vscode.workspace.applyEdit(we);
if (edt !== vscode.window.activeTextEditor) {
return this.skip();
}
@ -1168,6 +1166,5 @@ suite('vscode API - workspace', () => {
assert.ok(success);
assert.strictEqual(document.getText(), 'foobarhello\nworld');
assert.deepStrictEqual(edt.selections, [new vscode.Selection(0, 0, 0, 3)]);
});
});

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.69.0",
"distro": "87b1d47dad0ad97ea5867ccfca4e43dd64a1bad2",
"version": "1.70.0",
"distro": "73c5eeb6818a9483d7a4bc2b9328223485a59de6",
"author": {
"name": "Microsoft Corporation"
},
@ -229,4 +229,4 @@
"elliptic": "^6.5.3",
"nwmatcher": "^1.4.4"
}
}
}

View file

@ -527,6 +527,16 @@ export class Grid<T extends IView = IView> extends Disposable {
return this.gridview.resizeView(location, size);
}
/**
* Returns whether all other {@link IView views} are at their minimum size.
*
* @param view The reference {@link IView view}.
*/
isViewSizeMaximized(view: T): boolean {
const location = this.getViewLocation(view);
return this.gridview.isViewSizeMaximized(location);
}
/**
* Get the size of a {@link IView view}.
*

View file

@ -592,6 +592,10 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
this.splitview.resizeView(index, size);
}
isChildSizeMaximized(index: number): boolean {
return this.splitview.isViewSizeMaximized(index);
}
distributeViewSizes(recursive = false): void {
this.splitview.distributeViewSizes();
@ -1431,6 +1435,27 @@ export class GridView implements IDisposable {
}
}
/**
* Returns whether all other {@link IView views} are at their minimum size.
*
* @param location The {@link GridLocation location} of the view.
*/
isViewSizeMaximized(location: GridLocation): boolean {
const [ancestors, node] = this.getNode(location);
if (!(node instanceof LeafNode)) {
throw new Error('Invalid location');
}
for (let i = 0; i < ancestors.length; i++) {
if (!ancestors[i].isChildSizeMaximized(location[i])) {
return false;
}
}
return true;
}
/**
* Distribute the size among all {@link IView views} within the entire
* grid or within a single {@link SplitView}.

View file

@ -931,6 +931,23 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.state = State.Idle;
}
/**
* Returns whether all other {@link IView views} are at their minimum size.
*/
isViewSizeMaximized(index: number): boolean {
if (index < 0 || index >= this.viewItems.length) {
return false;
}
for (const item of this.viewItems) {
if (item !== this.viewItems[index] && item.size > item.minimumSize) {
return false;
}
}
return true;
}
/**
* Distribute the entire {@link SplitView} size among all {@link IView views}.
*/

View file

@ -52,7 +52,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/;
const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extension-host/;
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
const WINDOWS_PTY = /\\pipe\\winpty-control/;
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;

View file

@ -450,6 +450,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _matchOnDescription = false;
private _matchOnDetail = false;
private _matchOnLabel = true;
private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
private _sortByLabel = true;
private _autoFocusOnList = true;
private _keepScrollPosition = false;
@ -595,6 +596,15 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.update();
}
get matchOnLabelMode() {
return this._matchOnLabelMode;
}
set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') {
this._matchOnLabelMode = matchOnLabelMode;
this.update();
}
get sortByLabel() {
return this._sortByLabel;
}
@ -994,6 +1004,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel;
this.ui.list.matchOnLabelMode = this.matchOnLabelMode;
this.ui.list.sortByLabel = this.sortByLabel;
if (this.itemsUpdated) {
this.itemsUpdated = false;

View file

@ -17,10 +17,11 @@ import { compareAnything } from 'vs/base/common/comparers';
import { memoize } from 'vs/base/common/decorators';
import { Emitter, Event } from 'vs/base/common/event';
import { IMatch } from 'vs/base/common/filters';
import { matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
import { IParsedLabelWithIcons, matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
import { KeyCode } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { ltrim } from 'vs/base/common/strings';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput';
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
@ -258,6 +259,7 @@ export class QuickInputList {
matchOnDescription = false;
matchOnDetail = false;
matchOnLabel = true;
matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
matchOnMeta = true;
sortByLabel = true;
private readonly _onChangedAllVisibleChecked = new Emitter<boolean>();
@ -610,6 +612,8 @@ export class QuickInputList {
this.list.layout();
return false;
}
const queryWithWhitespace = query;
query = query.trim();
// Reset filtering
@ -628,7 +632,12 @@ export class QuickInputList {
else {
let currentSeparator: IQuickPickSeparator | undefined;
this.elements.forEach(element => {
const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
let labelHighlights: IMatch[] | undefined;
if (this.matchOnLabelMode === 'fuzzy') {
labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
} else {
labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesContiguousIconAware(queryWithWhitespace, parseLabelWithIcons(element.saneLabel))) : undefined;
}
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined;
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined;
const metaHighlights = this.matchOnMeta ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneMeta || ''))) : undefined;
@ -726,6 +735,43 @@ export class QuickInputList {
}
}
export function matchesContiguousIconAware(query: string, target: IParsedLabelWithIcons): IMatch[] | null {
const { text, iconOffsets } = target;
// Return early if there are no icon markers in the word to match against
if (!iconOffsets || iconOffsets.length === 0) {
return matchesContiguous(query, text);
}
// Trim the word to match against because it could have leading
// whitespace now if the word started with an icon
const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
// match on value without icon
const matches = matchesContiguous(query, wordToMatchAgainstWithoutIconsTrimmed);
// Map matches back to offsets with icon and trimming
if (matches) {
for (const match of matches) {
const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
match.start += iconOffset;
match.end += iconOffset;
}
}
return matches;
}
function matchesContiguous(word: string, wordToMatchAgainst: string): IMatch[] | null {
const matchIndex = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
if (matchIndex !== -1) {
return [{ start: matchIndex, end: matchIndex + word.length }];
}
return null;
}
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
const labelHighlightsA = elementA.labelHighlights || [];

View file

@ -292,6 +292,13 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
matchOnLabel: boolean;
/**
* The mode to filter label with. Fuzzy will use fuzzy searching and
* contiguous will make filter entries that do not contain the exact string
* (including whitespace). This defaults to `'fuzzy'`.
*/
matchOnLabelMode: 'fuzzy' | 'contiguous';
sortByLabel: boolean;
autoFocusOnList: boolean;

View file

@ -852,12 +852,12 @@ suite('Map', () => {
for (const item of keys) {
tst.set(item, true);
assert.ok(tst._isBalanced());
assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`);
}
for (const item of keys) {
tst.delete(item);
assert.ok(tst._isBalanced());
assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`);
}
}
});

View file

@ -5,11 +5,12 @@
import * as dom from 'vs/base/browser/dom';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { IDisposable, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { ICodeEditorOpenHandler, ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, InjectedTextOptions, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
@ -42,6 +43,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
protected _globalStyleSheet: GlobalStyleSheet | null;
private readonly _decorationOptionProviders = new Map<string, IModelDecorationOptionsProvider>();
private readonly _editorStyleSheets = new Map<string, RefCountedStyleSheet>();
private readonly _codeEditorOpenHandlers = new LinkedList<ICodeEditorOpenHandler>();
constructor(
@IThemeService private readonly _themeService: IThemeService,
@ -247,7 +249,21 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
}
abstract getActiveCodeEditor(): ICodeEditor | null;
abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
for (const handler of this._codeEditorOpenHandlers) {
const candidate = await handler(input, source, sideBySide);
if (candidate !== null) {
return candidate;
}
}
return null;
}
registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable {
const rm = this._codeEditorOpenHandlers.unshift(handler);
return toDisposable(rm);
}
}
export class ModelTransientSettingWatcher {

View file

@ -10,6 +10,7 @@ import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
export const ICodeEditorService = createDecorator<ICodeEditorService>('codeEditorService');
@ -53,4 +54,9 @@ export interface ICodeEditorService {
getActiveCodeEditor(): ICodeEditor | null;
openCodeEditor(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable;
}
export interface ICodeEditorOpenHandler {
(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
}

View file

@ -2562,12 +2562,12 @@ class EditorInlayHints extends BaseEditorOption<EditorOption.inlayHints, IEditor
'editor.inlayHints.fontSize': {
type: 'number',
default: defaults.fontSize,
markdownDescription: nls.localize('inlayHints.fontSize', "Controls font size of inlay hints in the editor. As default the `#editor.fontSize#` is used when the configured value is less than `5` or greater than the editor font size.")
markdownDescription: nls.localize('inlayHints.fontSize', "Controls font size of inlay hints in the editor. As default the {0} is used when the configured value is less than {1} or greater than the editor font size.", '`#editor.fontSize#`', '`5`')
},
'editor.inlayHints.fontFamily': {
type: 'string',
default: defaults.fontFamily,
markdownDescription: nls.localize('inlayHints.fontFamily', "Controls font family of inlay hints in the editor. When set to empty, the `#editor.fontFamily#` is used.")
markdownDescription: nls.localize('inlayHints.fontFamily', "Controls font family of inlay hints in the editor. When set to empty, the {0} is used.", '`#editor.fontFamily#`')
},
'editor.inlayHints.padding': {
type: 'boolean',
@ -3643,7 +3643,7 @@ class BracketPairColorization extends BaseEditorOption<EditorOption.bracketPairC
'editor.bracketPairColorization.enabled': {
type: 'boolean',
default: defaults.enabled,
markdownDescription: nls.localize('bracketPairColorization.enabled', "Controls whether bracket pair colorization is enabled or not. Use `#workbench.colorCustomizations#` to override the bracket highlight colors.")
markdownDescription: nls.localize('bracketPairColorization.enabled', "Controls whether bracket pair colorization is enabled or not. Use {0} to override the bracket highlight colors.", '`#workbench.colorCustomizations#`')
},
'editor.bracketPairColorization.independentColorPoolPerBracketType': {
type: 'boolean',
@ -4897,7 +4897,7 @@ export const EditorOptions = {
'- `ctrlCmd` refers to a value the setting can take and should not be localized.',
'- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.'
]
}, "The modifier to be used to add multiple cursors with the mouse. The Go to Definition and Open Link mouse gestures will adapt such that they do not conflict with the multicursor modifier. [Read more](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).")
}, "The modifier to be used to add multiple cursors with the mouse. The Go to Definition and Open Link mouse gestures will adapt such that they do not conflict with the [multicursor modifier](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).")
}
)),
multiCursorPaste: register(new EditorStringEnumOption(
@ -5088,12 +5088,12 @@ export const EditorOptions = {
suggestFontSize: register(new EditorIntOption(
EditorOption.suggestFontSize, 'suggestFontSize',
0, 0, 1000,
{ markdownDescription: nls.localize('suggestFontSize', "Font size for the suggest widget. When set to `0`, the value of `#editor.fontSize#` is used.") }
{ markdownDescription: nls.localize('suggestFontSize', "Font size for the suggest widget. When set to {0}, the value of {1} is used.", '`0`', '`#editor.fontSize#`') }
)),
suggestLineHeight: register(new EditorIntOption(
EditorOption.suggestLineHeight, 'suggestLineHeight',
0, 0, 1000,
{ markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to `0`, the value of `#editor.lineHeight#` is used. The minimum value is 8.") }
{ markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to {0}, the value of {1} is used. The minimum value is 8.", '`0`', '`#editor.lineHeight#`') }
)),
suggestOnTriggerCharacters: register(new EditorBooleanOption(
EditorOption.suggestOnTriggerCharacters, 'suggestOnTriggerCharacters', true,

View file

@ -13,7 +13,7 @@ import { IRange } from 'vs/editor/common/core/range';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@ -31,6 +31,13 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService {
this.onCodeEditorRemove(() => this._checkContextKey());
this._editorIsOpen = contextKeyService.createKey('editorIsOpen', false);
this._activeCodeEditor = null;
this.registerCodeEditorOpenHandler(async (input, source, sideBySide) => {
if (!source) {
return null;
}
return this.doOpenEditor(source, input);
});
}
private _checkContextKey(): void {
@ -52,13 +59,6 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService {
return this._activeCodeEditor;
}
public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
if (!source) {
return Promise.resolve(null);
}
return Promise.resolve(this.doOpenEditor(source, input));
}
private doOpenEditor(editor: ICodeEditor, input: ITextResourceEditorInput): ICodeEditor | null {
const model = this.findModel(editor, input.resource);

View file

@ -17,13 +17,13 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { asCssVariableName, ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry';
import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IProductIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { ColorScheme, isDark } from 'vs/platform/theme/common/theme';
import { ColorScheme, isDark, isHighContrast } from 'vs/platform/theme/common/theme';
import { getIconsStyleSheet, UnthemedProductIconTheme } from 'vs/platform/theme/browser/iconsStyleSheet';
const VS_THEME_NAME = 'vs';
const VS_DARK_THEME_NAME = 'vs-dark';
const HC_BLACK_THEME_NAME = 'hc-black';
const HC_LIGHT_THEME_NAME = 'hc-light';
export const VS_LIGHT_THEME_NAME = 'vs';
export const VS_DARK_THEME_NAME = 'vs-dark';
export const HC_BLACK_THEME_NAME = 'hc-black';
export const HC_LIGHT_THEME_NAME = 'hc-light';
const colorRegistry = Registry.as<IColorRegistry>(Extensions.ColorContribution);
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);
@ -118,7 +118,7 @@ class StandaloneTheme implements IStandaloneTheme {
public get type(): ColorScheme {
switch (this.base) {
case VS_THEME_NAME: return ColorScheme.LIGHT;
case VS_LIGHT_THEME_NAME: return ColorScheme.LIGHT;
case HC_BLACK_THEME_NAME: return ColorScheme.HIGH_CONTRAST_DARK;
case HC_LIGHT_THEME_NAME: return ColorScheme.HIGH_CONTRAST_LIGHT;
default: return ColorScheme.DARK;
@ -182,7 +182,7 @@ class StandaloneTheme implements IStandaloneTheme {
function isBuiltinTheme(themeName: string): themeName is BuiltinTheme {
return (
themeName === VS_THEME_NAME
themeName === VS_LIGHT_THEME_NAME
|| themeName === VS_DARK_THEME_NAME
|| themeName === HC_BLACK_THEME_NAME
|| themeName === HC_LIGHT_THEME_NAME
@ -191,7 +191,7 @@ function isBuiltinTheme(themeName: string): themeName is BuiltinTheme {
function getBuiltinRules(builtinTheme: BuiltinTheme): IStandaloneThemeData {
switch (builtinTheme) {
case VS_THEME_NAME:
case VS_LIGHT_THEME_NAME:
return vs;
case VS_DARK_THEME_NAME:
return vs_dark;
@ -229,7 +229,6 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
private _globalStyleElement: HTMLStyleElement | null;
private _styleElements: HTMLStyleElement[];
private _colorMapOverride: Color[] | null;
private _desiredTheme!: IStandaloneTheme;
private _theme!: IStandaloneTheme;
private _builtInProductIconTheme = new UnthemedProductIconTheme();
@ -240,7 +239,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
this._autoDetectHighContrast = true;
this._knownThemes = new Map<string, StandaloneTheme>();
this._knownThemes.set(VS_THEME_NAME, newBuiltInTheme(VS_THEME_NAME));
this._knownThemes.set(VS_LIGHT_THEME_NAME, newBuiltInTheme(VS_LIGHT_THEME_NAME));
this._knownThemes.set(VS_DARK_THEME_NAME, newBuiltInTheme(VS_DARK_THEME_NAME));
this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME));
this._knownThemes.set(HC_LIGHT_THEME_NAME, newBuiltInTheme(HC_LIGHT_THEME_NAME));
@ -253,7 +252,8 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
this._globalStyleElement = null;
this._styleElements = [];
this._colorMapOverride = null;
this.setTheme(VS_THEME_NAME);
this.setTheme(VS_LIGHT_THEME_NAME);
this._onOSSchemeChanged();
iconsStyleSheet.onDidChange(() => {
this._codiconCSS = iconsStyleSheet.getCSS();
@ -261,7 +261,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
});
addMatchMediaChangeListener('(forced-colors: active)', () => {
this._updateActualTheme();
this._onOSSchemeChanged();
});
}
@ -331,41 +331,43 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
}
public setTheme(themeName: string): void {
let theme: StandaloneTheme;
let theme: StandaloneTheme | undefined;
if (this._knownThemes.has(themeName)) {
theme = this._knownThemes.get(themeName)!;
theme = this._knownThemes.get(themeName);
} else {
theme = this._knownThemes.get(VS_THEME_NAME)!;
theme = this._knownThemes.get(VS_LIGHT_THEME_NAME);
}
this._desiredTheme = theme;
this._updateActualTheme();
this._updateActualTheme(theme);
}
private getHighContrastTheme() {
if (isDark(this._desiredTheme.type)) {
return HC_BLACK_THEME_NAME;
} else {
return HC_LIGHT_THEME_NAME;
}
}
private _updateActualTheme(): void {
const theme = (
this._autoDetectHighContrast && window.matchMedia(`(forced-colors: active)`).matches
? this._knownThemes.get(this.getHighContrastTheme())!
: this._desiredTheme
);
if (this._theme === theme) {
private _updateActualTheme(desiredTheme: IStandaloneTheme | undefined): void {
if (!desiredTheme || this._theme === desiredTheme) {
// Nothing to do
return;
}
this._theme = theme;
this._theme = desiredTheme;
this._updateThemeOrColorMap();
}
private _onOSSchemeChanged() {
if (this._autoDetectHighContrast) {
const wantsHighContrast = window.matchMedia(`(forced-colors: active)`).matches;
if (wantsHighContrast !== isHighContrast(this._theme.type)) {
// switch to high contrast or non-high contrast but stick to dark or light
let newThemeName;
if (isDark(this._theme.type)) {
newThemeName = wantsHighContrast ? HC_BLACK_THEME_NAME : VS_DARK_THEME_NAME;
} else {
newThemeName = wantsHighContrast ? HC_LIGHT_THEME_NAME : VS_LIGHT_THEME_NAME;
}
this._updateActualTheme(this._knownThemes.get(newThemeName));
}
}
}
public setAutoDetectHighContrast(autoDetectHighContrast: boolean): void {
this._autoDetectHighContrast = autoDetectHighContrast;
this._updateActualTheme();
this._onOSSchemeChanged();
}
private _updateThemeOrColorMap(): void {

View file

@ -7,7 +7,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneTheme';
import { ToggleHighContrastNLS } from 'vs/editor/common/standaloneStrings';
import { isHighContrast } from 'vs/platform/theme/common/theme';
import { isDark, isHighContrast } from 'vs/platform/theme/common/theme';
import { HC_BLACK_THEME_NAME, HC_LIGHT_THEME_NAME, VS_DARK_THEME_NAME, VS_LIGHT_THEME_NAME } from 'vs/editor/standalone/browser/standaloneThemeService';
class ToggleHighContrast extends EditorAction {
@ -25,13 +26,14 @@ class ToggleHighContrast extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
const standaloneThemeService = accessor.get(IStandaloneThemeService);
if (isHighContrast(standaloneThemeService.getColorTheme().type)) {
const currentTheme = standaloneThemeService.getColorTheme();
if (isHighContrast(currentTheme.type)) {
// We must toggle back to the integrator's theme
standaloneThemeService.setTheme(this._originalThemeName || 'vs');
standaloneThemeService.setTheme(this._originalThemeName || (isDark(currentTheme.type) ? VS_DARK_THEME_NAME : VS_LIGHT_THEME_NAME));
this._originalThemeName = null;
} else {
this._originalThemeName = standaloneThemeService.getColorTheme().themeName;
standaloneThemeService.setTheme('hc-black');
standaloneThemeService.setTheme(isDark(currentTheme.type) ? HC_BLACK_THEME_NAME : HC_LIGHT_THEME_NAME);
this._originalThemeName = currentTheme.themeName;
}
}
}

View file

@ -22,7 +22,7 @@ export class TestCodeEditorService extends AbstractCodeEditorService {
return null;
}
public lastInput?: IResourceEditorInput;
openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
override openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
this.lastInput = input;
return Promise.resolve(null);
}

View file

@ -324,7 +324,7 @@ class UtilityExtensionHostProcess extends Disposable {
const modulePath = FileAccess.asFileUri('bootstrap-fork.js', require).fsPath;
const args: string[] = ['--type=extensionHost', '--skipWorkspaceStorageLock'];
const execArgv: string[] = opts.execArgv || [];
execArgv.push(`--vscode-utility-kind=extensionHost`);
execArgv.push(`--vscode-utility-kind=extension-host`);
const env: { [key: string]: any } = { ...opts.env };
// Make sure all values are strings, otherwise the process will not start

View file

@ -40,8 +40,8 @@ export const enum TerminalSettingId {
DefaultProfileMacOs = 'terminal.integrated.defaultProfile.osx',
DefaultProfileWindows = 'terminal.integrated.defaultProfile.windows',
UseWslProfiles = 'terminal.integrated.useWslProfiles',
TabsDefaultIconColor = 'terminal.integrated.tabs.defaultIconColor',
TabsDefaultIconId = 'terminal.integrated.tabs.defaultIconId',
TabsDefaultColor = 'terminal.integrated.tabs.defaultColor',
TabsDefaultIcon = 'terminal.integrated.tabs.defaultIcon',
TabsEnabled = 'terminal.integrated.tabs.enabled',
TabsEnableAnimation = 'terminal.integrated.tabs.enableAnimation',
TabsHideCondition = 'terminal.integrated.tabs.hideCondition',
@ -443,11 +443,11 @@ export interface IShellLaunchConfig {
/**
* A string including ANSI escape sequences that will be written to the terminal emulator
* _before_ the terminal process has launched, a trailing \n is added at the end of the string.
* This allows for example the terminal instance to display a styled message as the first line
* of the terminal. Use \x1b over \033 or \e for the escape control character.
* _before_ the terminal process has launched, when a string is specified, a trailing \n is
* added at the end. This allows for example the terminal instance to display a styled message
* as the first line of the terminal. Use \x1b over \033 or \e for the escape control character.
*/
initialText?: string;
initialText?: string | { text: string; trailingNewLine: boolean };
/**
* Custom PTY/pseudoterminal process to use.

View file

@ -46,11 +46,11 @@ const terminalProfileBaseProperties: IJSONSchemaMap = {
type: 'boolean'
},
icon: {
description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
description: localize('terminalProfile.icon', 'A codicon ID to associate with the terminal icon.'),
...terminalIconSchema
},
color: {
description: localize('terminalProfile.color', 'A theme color ID to associate with this terminal.'),
description: localize('terminalProfile.color', 'A theme color ID to associate with the terminal icon.'),
...terminalColorSchema
},
env: {

View file

@ -190,7 +190,7 @@ export class PtyService extends Disposable implements IPtyService {
executableEnv,
options
};
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, rawReviveBuffer, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving && typeof shellLaunchConfig.initialText === 'string' ? shellLaunchConfig.initialText : undefined, rawReviveBuffer, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
process.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
process.onProcessExit(event => {
persistentProcess.dispose();

View file

@ -223,6 +223,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
const folderUri = folder ? uri.revive(folder) : undefined;
const launch = this.debugService.getConfigurationManager().getLaunch(folderUri);
const parentSession = this.getSession(options.parentSessionID);
const saveBeforeStart = typeof options.suppressSaveBeforeStart === 'boolean' ? !options.suppressSaveBeforeStart : undefined;
const debugOptions: IDebugSessionOptions = {
noDebug: options.noDebug,
parentSession,
@ -230,11 +231,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
repl: options.repl,
compact: options.compact,
debugUI: options.debugUI,
compoundRoot: parentSession?.compoundRoot
compoundRoot: parentSession?.compoundRoot,
saveBeforeStart: saveBeforeStart
};
try {
const saveBeforeStart = typeof options.suppressSaveBeforeStart === 'boolean' ? !options.suppressSaveBeforeStart : undefined;
return this.debugService.startDebugging(launch, nameOrConfig, debugOptions, saveBeforeStart);
return this.debugService.startDebugging(launch, nameOrConfig, debugOptions);
} catch (err) {
throw new ErrorNoTelemetry(err && err.message ? err.message : 'cannot start debugging');
}

View file

@ -16,6 +16,7 @@ import { FileOperation } from 'vs/platform/files/common/files';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
class FileSystemWatcher implements vscode.FileSystemWatcher {
@ -223,14 +224,14 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: IWaitUntilData<E>, timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> {
const extensionNames = new Set<string>();
const edits: WorkspaceEdit[] = [];
const edits: [IExtensionDescription, WorkspaceEdit][] = [];
await emitter.fireAsync(data, token, async (thenable, listener) => {
await emitter.fireAsync(data, token, async (thenable: Promise<unknown>, listener) => {
// ignore all results except for WorkspaceEdits. Those are stored in an array.
const now = Date.now();
const result = await Promise.resolve(thenable);
if (result instanceof WorkspaceEdit) {
edits.push(result);
edits.push([(<IExtensionListener<E>>listener).extension, result]);
extensionNames.add((<IExtensionListener<E>>listener).extension.displayName ?? (<IExtensionListener<E>>listener).extension.identifier.value);
}
@ -249,11 +250,11 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
// concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer
const dto: IWorkspaceEditDto = { edits: [] };
for (const edit of edits) {
for (const [extension, edit] of edits) {
const { edits } = typeConverter.WorkspaceEdit.from(edit, {
getTextDocumentVersion: uri => this._extHostDocumentsAndEditors.getDocument(uri)?.version,
getNotebookDocumentVersion: () => undefined,
});
}, isProposedApiEnabled(extension, 'snippetWorkspaceEdit'));
dto.edits = dto.edits.concat(edits);
}
return { edit: dto, extensionNames: Array.from(extensionNames) };

View file

@ -446,7 +446,7 @@ class CodeActionAdapter {
title: candidate.title,
command: candidate.command && this._commands.toInternal(candidate.command, disposables),
diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from),
edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit),
edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit')),
kind: candidate.kind && candidate.kind.value,
isPreferred: candidate.isPreferred,
disabled: candidate.disabled?.reason
@ -467,7 +467,7 @@ class CodeActionAdapter {
}
const resolvedItem = (await this._provider.resolveCodeAction(item, token)) ?? item;
return resolvedItem?.edit
? typeConvert.WorkspaceEdit.from(resolvedItem.edit)
? typeConvert.WorkspaceEdit.from(resolvedItem.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit'))
: undefined;
}
@ -522,7 +522,7 @@ class DocumentPasteEditProvider {
return {
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined,
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined,
};
}
}
@ -1808,7 +1808,7 @@ class DocumentOnDropEditAdapter {
}
return {
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined,
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined,
};
}
}

View file

@ -598,17 +598,28 @@ export namespace WorkspaceEdit {
} else if (entry._type === types.FileEditType.Text) {
// text edits
const edit = <languages.IWorkspaceTextEdit>{
result.edits.push(<languages.IWorkspaceTextEdit>{
resource: entry.uri,
textEdit: TextEdit.from(entry.edit),
versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined,
metadata: entry.metadata
};
if (allowSnippetTextEdit && entry.edit.newText2 instanceof types.SnippetString) {
edit.textEdit.insertAsSnippet = true;
edit.textEdit.text = entry.edit.newText2.value;
});
} else if (entry._type === types.FileEditType.Snippet) {
// snippet text edits
if (!allowSnippetTextEdit) {
console.warn(`DROPPING snippet text edit because proposal IS NOT ENABLED`, entry);
continue;
}
result.edits.push(edit);
result.edits.push(<languages.IWorkspaceTextEdit>{
resource: entry.uri,
textEdit: {
range: Range.from(entry.range),
text: entry.edit.value,
insertAsSnippet: true
},
versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined,
metadata: entry.metadata
});
} else if (entry._type === types.FileEditType.Cell) {
// cell edit

View file

@ -19,16 +19,6 @@ import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol';
import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as vscode from 'vscode';
function es5ClassCompat(target: Function): any {
///@ts-expect-error
function _() { return Reflect.construct(target, arguments, this.constructor); }
Object.defineProperty(_, 'name', Object.getOwnPropertyDescriptor(target, 'name')!);
Object.setPrototypeOf(_, target);
Object.setPrototypeOf(_.prototype, target.prototype);
return _;
}
@es5ClassCompat
export class Disposable {
static from(...inDisposables: { dispose(): any }[]): Disposable {
@ -59,7 +49,6 @@ export class Disposable {
}
}
@es5ClassCompat
export class Position {
static Min(...positions: Position[]): Position {
@ -240,7 +229,6 @@ export class Position {
}
}
@es5ClassCompat
export class Range {
static isRange(thing: any): thing is vscode.Range {
@ -386,7 +374,6 @@ export class Range {
}
}
@es5ClassCompat
export class Selection extends Range {
static isSelection(thing: any): thing is Selection {
@ -515,7 +502,6 @@ export enum EnvironmentVariableMutatorType {
Prepend = 3
}
@es5ClassCompat
export class TextEdit {
static isTextEdit(thing: any): thing is TextEdit {
@ -549,7 +535,6 @@ export class TextEdit {
protected _range: Range;
protected _newText: string | null;
newText2?: string | SnippetString;
protected _newEol?: EndOfLine;
get range(): Range {
@ -599,7 +584,6 @@ export class TextEdit {
}
}
@es5ClassCompat
export class NotebookEdit implements vscode.NotebookEdit {
static isNotebookCellEdit(thing: any): thing is NotebookEdit {
@ -660,6 +644,7 @@ export const enum FileEditType {
Text = 2,
Cell = 3,
CellReplace = 5,
Snippet = 6,
}
export interface IFileOperation {
@ -677,6 +662,14 @@ export interface IFileTextEdit {
metadata?: vscode.WorkspaceEditEntryMetadata;
}
export interface IFileSnippetTextEdit {
_type: FileEditType.Snippet;
uri: URI;
range: vscode.Range;
edit: vscode.SnippetString;
metadata?: vscode.WorkspaceEditEntryMetadata;
}
export interface IFileCellEdit {
_type: FileEditType.Cell;
uri: URI;
@ -695,9 +688,8 @@ export interface ICellEdit {
}
type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileCellEdit | ICellEdit;
type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileSnippetTextEdit | IFileCellEdit | ICellEdit;
@es5ClassCompat
export class WorkspaceEdit implements vscode.WorkspaceEdit {
private readonly _edits: WorkspaceEditEntry[] = [];
@ -762,8 +754,12 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
// --- text
replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: FileEditType.Text, uri, edit: new TextEdit(range, newText), metadata });
replace(uri: URI, range: Range, newText: string | vscode.SnippetString, metadata?: vscode.WorkspaceEditEntryMetadata): void {
if (typeof newText === 'string') {
this._edits.push({ _type: FileEditType.Text, uri, edit: new TextEdit(range, newText), metadata });
} else {
this._edits.push({ _type: FileEditType.Snippet, uri, range, edit: newText, metadata });
}
}
insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void {
@ -844,7 +840,6 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
}
}
@es5ClassCompat
export class SnippetString {
static isSnippetString(thing: any): thing is SnippetString {
@ -951,7 +946,6 @@ export enum DiagnosticSeverity {
Error = 0
}
@es5ClassCompat
export class Location {
static isLocation(thing: any): thing is vscode.Location {
@ -990,7 +984,6 @@ export class Location {
}
}
@es5ClassCompat
export class DiagnosticRelatedInformation {
static is(thing: any): thing is DiagnosticRelatedInformation {
@ -1024,7 +1017,6 @@ export class DiagnosticRelatedInformation {
}
}
@es5ClassCompat
export class Diagnostic {
range: Range;
@ -1075,7 +1067,6 @@ export class Diagnostic {
}
}
@es5ClassCompat
export class Hover {
public contents: (vscode.MarkdownString | vscode.MarkedString)[];
@ -1103,7 +1094,6 @@ export enum DocumentHighlightKind {
Write = 2
}
@es5ClassCompat
export class DocumentHighlight {
range: Range;
@ -1155,7 +1145,6 @@ export enum SymbolTag {
Deprecated = 1,
}
@es5ClassCompat
export class SymbolInformation {
static validate(candidate: SymbolInformation): void {
@ -1200,7 +1189,6 @@ export class SymbolInformation {
}
}
@es5ClassCompat
export class DocumentSymbol {
static validate(candidate: DocumentSymbol): void {
@ -1239,7 +1227,6 @@ export enum CodeActionTriggerKind {
Automatic = 2,
}
@es5ClassCompat
export class CodeAction {
title: string;
@ -1260,7 +1247,6 @@ export class CodeAction {
}
@es5ClassCompat
export class CodeActionKind {
private static readonly sep = '.';
@ -1300,7 +1286,6 @@ CodeActionKind.Source = CodeActionKind.Empty.append('source');
CodeActionKind.SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
CodeActionKind.SourceFixAll = CodeActionKind.Source.append('fixAll');
@es5ClassCompat
export class SelectionRange {
range: Range;
@ -1367,7 +1352,6 @@ export enum LanguageStatusSeverity {
}
@es5ClassCompat
export class CodeLens {
range: Range;
@ -1384,7 +1368,6 @@ export class CodeLens {
}
}
@es5ClassCompat
export class MarkdownString implements vscode.MarkdownString {
readonly #delegate: BaseMarkdownString;
@ -1455,7 +1438,6 @@ export class MarkdownString implements vscode.MarkdownString {
}
}
@es5ClassCompat
export class ParameterInformation {
label: string | [number, number];
@ -1467,7 +1449,6 @@ export class ParameterInformation {
}
}
@es5ClassCompat
export class SignatureInformation {
label: string;
@ -1482,7 +1463,6 @@ export class SignatureInformation {
}
}
@es5ClassCompat
export class SignatureHelp {
signatures: SignatureInformation[];
@ -1506,7 +1486,6 @@ export enum InlayHintKind {
Parameter = 2,
}
@es5ClassCompat
export class InlayHintLabelPart {
value: string;
@ -1519,7 +1498,6 @@ export class InlayHintLabelPart {
}
}
@es5ClassCompat
export class InlayHint implements vscode.InlayHint {
label: string | InlayHintLabelPart[];
@ -1588,7 +1566,6 @@ export interface CompletionItemLabel {
description?: string;
}
@es5ClassCompat
export class CompletionItem implements vscode.CompletionItem {
label: string | CompletionItemLabel;
@ -1627,7 +1604,6 @@ export class CompletionItem implements vscode.CompletionItem {
}
}
@es5ClassCompat
export class CompletionList {
isIncomplete?: boolean;
@ -1639,7 +1615,6 @@ export class CompletionList {
}
}
@es5ClassCompat
export class InlineSuggestion implements vscode.InlineCompletionItem {
filterText?: string;
@ -1654,7 +1629,6 @@ export class InlineSuggestion implements vscode.InlineCompletionItem {
}
}
@es5ClassCompat
export class InlineSuggestionList implements vscode.InlineCompletionList {
items: vscode.InlineCompletionItemNew[];
@ -1665,7 +1639,6 @@ export class InlineSuggestionList implements vscode.InlineCompletionList {
}
}
@es5ClassCompat
export class InlineSuggestionNew implements vscode.InlineCompletionItemNew {
insertText: string;
range?: Range;
@ -1678,7 +1651,6 @@ export class InlineSuggestionNew implements vscode.InlineCompletionItemNew {
}
}
@es5ClassCompat
export class InlineSuggestionsNew implements vscode.InlineCompletionListNew {
items: vscode.InlineCompletionItemNew[];
@ -1772,7 +1744,6 @@ export namespace TextEditorSelectionChangeKind {
}
}
@es5ClassCompat
export class DocumentLink {
range: Range;
@ -1793,7 +1764,6 @@ export class DocumentLink {
}
}
@es5ClassCompat
export class Color {
readonly red: number;
readonly green: number;
@ -1810,7 +1780,6 @@ export class Color {
export type IColorFormat = string | { opaque: string; transparent: string };
@es5ClassCompat
export class ColorInformation {
range: Range;
@ -1828,7 +1797,6 @@ export class ColorInformation {
}
}
@es5ClassCompat
export class ColorPresentation {
label: string;
textEdit?: TextEdit;
@ -1903,7 +1871,6 @@ export enum TaskPanelKind {
New = 3
}
@es5ClassCompat
export class TaskGroup implements vscode.TaskGroup {
isDefault: boolean | undefined;
@ -1955,7 +1922,6 @@ function computeTaskExecutionId(values: string[]): string {
return id;
}
@es5ClassCompat
export class ProcessExecution implements vscode.ProcessExecution {
private _process: string;
@ -2026,7 +1992,6 @@ export class ProcessExecution implements vscode.ProcessExecution {
}
}
@es5ClassCompat
export class ShellExecution implements vscode.ShellExecution {
private _commandLine: string | undefined;
@ -2141,7 +2106,6 @@ export class CustomExecution implements vscode.CustomExecution {
}
}
@es5ClassCompat
export class Task implements vscode.Task {
private static ExtensionCallbackType: string = 'customExecution';
@ -2398,7 +2362,6 @@ export enum ProgressLocation {
Notification = 15
}
@es5ClassCompat
export class TreeItem {
label?: string | vscode.TreeItemLabel;
@ -2426,7 +2389,6 @@ export enum TreeItemCollapsibleState {
Expanded = 2
}
@es5ClassCompat
export class DataTransferItem {
async asString(): Promise<string> {
@ -2440,7 +2402,6 @@ export class DataTransferItem {
constructor(public readonly value: any) { }
}
@es5ClassCompat
export class DataTransfer implements vscode.DataTransfer {
#items = new Map<string, DataTransferItem[]>();
@ -2482,7 +2443,6 @@ export class DataTransfer implements vscode.DataTransfer {
}
}
@es5ClassCompat
export class DocumentDropEdit {
insertText: string | SnippetString;
@ -2493,7 +2453,6 @@ export class DocumentDropEdit {
}
}
@es5ClassCompat
export class DocumentPasteEdit {
insertText: string | SnippetString;
@ -2504,7 +2463,6 @@ export class DocumentPasteEdit {
}
}
@es5ClassCompat
export class ThemeIcon {
static File: ThemeIcon;
@ -2522,7 +2480,6 @@ ThemeIcon.File = new ThemeIcon('file');
ThemeIcon.Folder = new ThemeIcon('folder');
@es5ClassCompat
export class ThemeColor {
id: string;
constructor(id: string) {
@ -2538,7 +2495,6 @@ export enum ConfigurationTarget {
WorkspaceFolder = 3
}
@es5ClassCompat
export class RelativePattern implements IRelativePattern {
pattern: string;
@ -2592,7 +2548,6 @@ export class RelativePattern implements IRelativePattern {
}
}
@es5ClassCompat
export class Breakpoint {
private _id: string | undefined;
@ -2623,7 +2578,6 @@ export class Breakpoint {
}
}
@es5ClassCompat
export class SourceBreakpoint extends Breakpoint {
readonly location: Location;
@ -2636,7 +2590,6 @@ export class SourceBreakpoint extends Breakpoint {
}
}
@es5ClassCompat
export class FunctionBreakpoint extends Breakpoint {
readonly functionName: string;
@ -2646,7 +2599,6 @@ export class FunctionBreakpoint extends Breakpoint {
}
}
@es5ClassCompat
export class DataBreakpoint extends Breakpoint {
readonly label: string;
readonly dataId: string;
@ -2664,7 +2616,6 @@ export class DataBreakpoint extends Breakpoint {
}
@es5ClassCompat
export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
readonly command: string;
readonly args: string[];
@ -2677,7 +2628,6 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
}
}
@es5ClassCompat
export class DebugAdapterServer implements vscode.DebugAdapterServer {
readonly port: number;
readonly host?: string;
@ -2688,13 +2638,11 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer {
}
}
@es5ClassCompat
export class DebugAdapterNamedPipeServer implements vscode.DebugAdapterNamedPipeServer {
constructor(public readonly path: string) {
}
}
@es5ClassCompat
export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation {
readonly implementation: vscode.DebugAdapter;
@ -2703,7 +2651,6 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli
}
}
@es5ClassCompat
export class EvaluatableExpression implements vscode.EvaluatableExpression {
readonly range: vscode.Range;
readonly expression?: string;
@ -2724,7 +2671,6 @@ export enum InlineCompletionTriggerKindNew {
Automatic = 1,
}
@es5ClassCompat
export class InlineValueText implements vscode.InlineValueText {
readonly range: Range;
readonly text: string;
@ -2735,7 +2681,6 @@ export class InlineValueText implements vscode.InlineValueText {
}
}
@es5ClassCompat
export class InlineValueVariableLookup implements vscode.InlineValueVariableLookup {
readonly range: Range;
readonly variableName?: string;
@ -2748,7 +2693,6 @@ export class InlineValueVariableLookup implements vscode.InlineValueVariableLook
}
}
@es5ClassCompat
export class InlineValueEvaluatableExpression implements vscode.InlineValueEvaluatableExpression {
readonly range: Range;
readonly expression?: string;
@ -2759,7 +2703,6 @@ export class InlineValueEvaluatableExpression implements vscode.InlineValueEvalu
}
}
@es5ClassCompat
export class InlineValueContext implements vscode.InlineValueContext {
readonly frameId: number;
@ -2779,7 +2722,6 @@ export enum FileChangeType {
Deleted = 3,
}
@es5ClassCompat
export class FileSystemError extends Error {
static FileExists(messageOrUri?: string | URI): FileSystemError {
@ -2829,7 +2771,6 @@ export class FileSystemError extends Error {
//#region folding api
@es5ClassCompat
export class FoldingRange {
start: number;
@ -3119,7 +3060,6 @@ export enum DebugConsoleMode {
//#endregion
@es5ClassCompat
export class QuickInputButtons {
static readonly Back: vscode.QuickInputButton = { iconPath: new ThemeIcon('arrow-left') };
@ -3175,7 +3115,6 @@ export class FileDecoration {
//#region Theming
@es5ClassCompat
export class ColorTheme implements vscode.ColorTheme {
constructor(public readonly kind: ColorThemeKind) {
}
@ -3470,7 +3409,6 @@ export class NotebookRendererScript {
//#region Timeline
@es5ClassCompat
export class TimelineItem implements vscode.TimelineItem {
constructor(public label: string, public timestamp: number) { }
}
@ -3560,7 +3498,6 @@ export enum TestRunProfileKind {
Coverage = 3,
}
@es5ClassCompat
export class TestRunRequest implements vscode.TestRunRequest {
constructor(
public readonly include: vscode.TestItem[] | undefined = undefined,
@ -3569,7 +3506,6 @@ export class TestRunRequest implements vscode.TestRunRequest {
) { }
}
@es5ClassCompat
export class TestMessage implements vscode.TestMessage {
public expectedOutput?: string;
public actualOutput?: string;
@ -3585,7 +3521,6 @@ export class TestMessage implements vscode.TestMessage {
constructor(public message: string | vscode.MarkdownString) { }
}
@es5ClassCompat
export class TestTag implements vscode.TestTag {
constructor(public readonly id: string) { }
}
@ -3593,12 +3528,10 @@ export class TestTag implements vscode.TestTag {
//#endregion
//#region Test Coverage
@es5ClassCompat
export class CoveredCount implements vscode.CoveredCount {
constructor(public covered: number, public total: number) { }
}
@es5ClassCompat
export class FileCoverage implements vscode.FileCoverage {
public static fromDetails(uri: vscode.Uri, details: vscode.DetailedCoverage[]): vscode.FileCoverage {
const statements = new CoveredCount(0, 0);
@ -3642,7 +3575,6 @@ export class FileCoverage implements vscode.FileCoverage {
) { }
}
@es5ClassCompat
export class StatementCoverage implements vscode.StatementCoverage {
constructor(
public executionCount: number,
@ -3651,7 +3583,6 @@ export class StatementCoverage implements vscode.StatementCoverage {
) { }
}
@es5ClassCompat
export class BranchCoverage implements vscode.BranchCoverage {
constructor(
public executionCount: number,
@ -3659,7 +3590,6 @@ export class BranchCoverage implements vscode.BranchCoverage {
) { }
}
@es5ClassCompat
export class FunctionCoverage implements vscode.FunctionCoverage {
constructor(
public executionCount: number,

View file

@ -135,8 +135,6 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
readonly titleHeight: IEditorGroupTitleHeight;
readonly isMinimized: boolean;
readonly disposed: boolean;
setActive(isActive: boolean): void;

View file

@ -573,24 +573,31 @@ abstract class AbstractCloseAllAction extends Action {
override async run(): Promise<void> {
// Depending on the editor and auto save configuration,
// split dirty editors into buckets
// split editors into buckets for handling confirmation
const dirtyEditorsWithDefaultConfirm = new Set<IEditorIdentifier>();
const dirtyAutoSaveOnFocusChangeEditors = new Set<IEditorIdentifier>();
const dirtyAutoSaveOnWindowChangeEditors = new Set<IEditorIdentifier>();
const dirtyEditorsWithCustomConfirm = new Map<string /* typeId */, Set<IEditorIdentifier>>();
const editorsWithCustomConfirm = new Map<string /* typeId */, Set<IEditorIdentifier>>();
for (const { editor, groupId } of this.editorService.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: this.excludeSticky })) {
if (!editor.isDirty() || editor.isSaving()) {
continue; // only interested in dirty editors that are not in the process of saving
let confirmClose = false;
if (editor.closeHandler) {
confirmClose = editor.closeHandler.showConfirm(); // custom handling of confirmation on close
} else {
confirmClose = editor.isDirty() && !editor.isSaving(); // default confirm only when dirty and not saving
}
if (!confirmClose) {
continue;
}
// Editor has custom confirm implementation
if (typeof editor.confirm === 'function') {
let customEditorsToConfirm = dirtyEditorsWithCustomConfirm.get(editor.typeId);
if (typeof editor.closeHandler?.confirm === 'function') {
let customEditorsToConfirm = editorsWithCustomConfirm.get(editor.typeId);
if (!customEditorsToConfirm) {
customEditorsToConfirm = new Set();
dirtyEditorsWithCustomConfirm.set(editor.typeId, customEditorsToConfirm);
editorsWithCustomConfirm.set(editor.typeId, customEditorsToConfirm);
}
customEditorsToConfirm.add({ editor, groupId });
@ -619,7 +626,7 @@ abstract class AbstractCloseAllAction extends Action {
if (dirtyEditorsWithDefaultConfirm.size > 0) {
const editors = Array.from(dirtyEditorsWithDefaultConfirm.values());
await this.revealDirtyEditors(editors); // help user make a decision by revealing editors
await this.revealEditorsToConfirm(editors); // help user make a decision by revealing editors
const confirmation = await this.fileDialogService.showSaveConfirm(editors.map(({ editor }) => {
if (editor instanceof SideBySideEditorInput) {
@ -642,12 +649,12 @@ abstract class AbstractCloseAllAction extends Action {
}
// 2.) Show custom confirm based dialog
for (const [, editorIdentifiers] of dirtyEditorsWithCustomConfirm) {
for (const [, editorIdentifiers] of editorsWithCustomConfirm) {
const editors = Array.from(editorIdentifiers.values());
await this.revealDirtyEditors(editors); // help user make a decision by revealing editors
await this.revealEditorsToConfirm(editors); // help user make a decision by revealing editors
const confirmation = await firstOrDefault(editors)?.editor.confirm?.(editors);
const confirmation = await firstOrDefault(editors)?.editor.closeHandler?.confirm?.(editors);
if (typeof confirmation === 'number') {
switch (confirmation) {
case ConfirmResult.CANCEL:
@ -683,7 +690,7 @@ abstract class AbstractCloseAllAction extends Action {
return this.doCloseAll();
}
private async revealDirtyEditors(editors: ReadonlyArray<IEditorIdentifier>): Promise<void> {
private async revealEditorsToConfirm(editors: ReadonlyArray<IEditorIdentifier>): Promise<void> {
try {
const handledGroups = new Set<GroupIdentifier>();
for (const { editor, groupId } of editors) {
@ -1016,7 +1023,7 @@ export class MinimizeOtherGroupsAction extends Action {
}
override async run(): Promise<void> {
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
this.editorGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE);
}
}
@ -1067,7 +1074,7 @@ export class MaximizeGroupAction extends Action {
if (this.editorService.activeEditor) {
this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);
this.layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART);
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
this.editorGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE);
}
}
}

View file

@ -781,14 +781,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this.titleAreaControl.getHeight();
}
get isMinimized(): boolean {
if (!this.dimension) {
return false;
}
return this.dimension.width === this.minimumWidth || this.dimension.height === this.minimumHeight;
}
notifyIndexChanged(newIndex: number): void {
if (this._index !== newIndex) {
this._index = newIndex;
@ -1322,16 +1314,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region closeEditor()
async closeEditor(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions): Promise<boolean> {
return this.doCloseEditorWithDirtyHandling(editor, options);
return this.doCloseEditorWithConfirmationHandling(editor, options);
}
private async doCloseEditorWithDirtyHandling(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions, internalOptions?: IInternalEditorCloseOptions): Promise<boolean> {
private async doCloseEditorWithConfirmationHandling(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions, internalOptions?: IInternalEditorCloseOptions): Promise<boolean> {
if (!editor) {
return false;
}
// Check for dirty and veto
const veto = await this.handleDirtyClosing([editor]);
// Check for confirmation and veto
const veto = await this.handleCloseConfirmation([editor]);
if (veto) {
return false;
}
@ -1461,7 +1453,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this.model.closeEditor(editor, internalOptions?.context)?.editorIndex;
}
private async handleDirtyClosing(editors: EditorInput[]): Promise<boolean /* veto */> {
private async handleCloseConfirmation(editors: EditorInput[]): Promise<boolean /* veto */> {
if (!editors.length) {
return false; // no veto
}
@ -1470,15 +1462,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// To prevent multiple confirmation dialogs from showing up one after the other
// we check if a pending confirmation is currently showing and if so, join that
let handleDirtyClosingPromise = this.mapEditorToPendingConfirmation.get(editor);
if (!handleDirtyClosingPromise) {
handleDirtyClosingPromise = this.doHandleDirtyClosing(editor);
this.mapEditorToPendingConfirmation.set(editor, handleDirtyClosingPromise);
let handleCloseConfirmationPromise = this.mapEditorToPendingConfirmation.get(editor);
if (!handleCloseConfirmationPromise) {
handleCloseConfirmationPromise = this.doHandleCloseConfirmation(editor);
this.mapEditorToPendingConfirmation.set(editor, handleCloseConfirmationPromise);
}
let veto: boolean;
try {
veto = await handleDirtyClosingPromise;
veto = await handleCloseConfirmationPromise;
} finally {
this.mapEditorToPendingConfirmation.delete(editor);
}
@ -1489,12 +1481,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Otherwise continue with the remainders
return this.handleDirtyClosing(editors);
return this.handleCloseConfirmation(editors);
}
private async doHandleDirtyClosing(editor: EditorInput, options?: { skipAutoSave: boolean }): Promise<boolean /* veto */> {
if (!editor.isDirty() || editor.isSaving()) {
return false; // editor must be dirty and not saving
private async doHandleCloseConfirmation(editor: EditorInput, options?: { skipAutoSave: boolean }): Promise<boolean /* veto */> {
if (!this.shouldConfirmClose(editor)) {
return false; // no veto
}
if (editor instanceof SideBySideEditorInput && this.model.contains(editor.primary)) {
@ -1531,10 +1523,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// on auto-save configuration.
// However, make sure to respect `skipAutoSave` option in case the automated
// save fails which would result in the editor never closing.
// Also, we only do this if no custom confirmation handling is implemented.
let confirmation = ConfirmResult.CANCEL;
let saveReason = SaveReason.EXPLICIT;
let autoSave = false;
if (!editor.hasCapability(EditorInputCapabilities.Untitled) && !options?.skipAutoSave) {
if (!editor.hasCapability(EditorInputCapabilities.Untitled) && !options?.skipAutoSave && !editor.closeHandler) {
// Auto-save on focus change: save, because a dialog would steal focus
// (see https://github.com/microsoft/vscode/issues/108752)
@ -1554,15 +1547,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
// No auto-save on focus change: ask user
// No auto-save on focus change or custom confirmation handler: ask user
if (!autoSave) {
// Switch to editor that we want to handle and confirm to save/revert
// Switch to editor that we want to handle for confirmation
await this.doOpenEditor(editor);
// Let editor handle confirmation if implemented
if (typeof editor.confirm === 'function') {
confirmation = await editor.confirm();
if (typeof editor.closeHandler?.confirm === 'function') {
confirmation = await editor.closeHandler.confirm();
}
// Show a file specific confirmation
@ -1578,11 +1571,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
// It could be that the editor saved meanwhile or is saving, so we check
// It could be that the editor's choice of confirmation has changed
// given the check for confirmation is long running, so we check
// again to see if anything needs to happen before closing for good.
// This can happen for example if autoSave: onFocusChange is configured
// This can happen for example if `autoSave: onFocusChange` is configured
// so that the save happens when the dialog opens.
if (!editor.isDirty() || editor.isSaving()) {
if (!this.shouldConfirmClose(editor)) {
return confirmation === ConfirmResult.CANCEL ? true : false;
}
@ -1595,7 +1589,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// we handle the dirty editor again but this time ensuring to
// show the confirm dialog
// (see https://github.com/microsoft/vscode/issues/108752)
return this.doHandleDirtyClosing(editor, { skipAutoSave: true });
return this.doHandleCloseConfirmation(editor, { skipAutoSave: true });
}
return editor.isDirty(); // veto if still dirty
@ -1621,6 +1615,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
private shouldConfirmClose(editor: EditorInput): boolean {
if (editor.closeHandler) {
return editor.closeHandler.showConfirm(); // custom handling of confirmation on close
}
return editor.isDirty() && !editor.isSaving(); // editor must be dirty and not saving
}
//#endregion
//#region closeEditors()
@ -1632,8 +1634,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const editors = this.doGetEditorsToClose(args);
// Check for dirty and veto
const veto = await this.handleDirtyClosing(editors.slice(0));
// Check for confirmation and veto
const veto = await this.handleCloseConfirmation(editors.slice(0));
if (veto) {
return false;
}
@ -1714,8 +1716,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return true;
}
// Check for dirty and veto
const veto = await this.handleDirtyClosing(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
// Check for confirmation and veto
const veto = await this.handleCloseConfirmation(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
if (veto) {
return false;
}
@ -1795,7 +1797,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.doCloseEditor(editor, false, { context: EditorCloseContext.REPLACE });
closed = true;
} else {
closed = await this.doCloseEditorWithDirtyHandling(editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
closed = await this.doCloseEditorWithConfirmationHandling(editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
}
if (!closed) {
@ -1815,7 +1817,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
if (activeReplacement.forceReplaceDirty) {
this.doCloseEditor(activeReplacement.editor, false, { context: EditorCloseContext.REPLACE });
} else {
await this.doCloseEditorWithDirtyHandling(activeReplacement.editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
await this.doCloseEditorWithConfirmationHandling(activeReplacement.editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
}
}

View file

@ -362,32 +362,22 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
case GroupsArrangement.EVEN:
this.gridWidget.distributeViewSizes();
break;
case GroupsArrangement.MINIMIZE_OTHERS:
case GroupsArrangement.MAXIMIZE:
this.gridWidget.maximizeViewSize(target);
break;
case GroupsArrangement.TOGGLE:
if (this.isGroupMaximized(target)) {
this.arrangeGroups(GroupsArrangement.EVEN);
} else {
this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
this.arrangeGroups(GroupsArrangement.MAXIMIZE);
}
break;
}
}
private isGroupMaximized(targetGroup: IEditorGroupView): boolean {
for (const group of this.groups) {
if (group === targetGroup) {
continue; // ignore target group
}
if (!group.isMinimized) {
return false; // target cannot be maximized if one group is not minimized
}
}
return true;
isGroupMaximized(targetGroup: IEditorGroupView): boolean {
return this.gridWidget.isViewSizeMaximized(targetGroup);
}
setGroupOrientation(orientation: GroupOrientation): void {
@ -609,7 +599,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
if (this.gridWidget) {
const viewSize = this.gridWidget.getViewSize(group);
if (viewSize.width === group.minimumWidth || viewSize.height === group.minimumHeight) {
this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS, group);
this.arrangeGroups(GroupsArrangement.MAXIMIZE, group);
}
}
}

View file

@ -245,7 +245,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
},
'workbench.editor.restoreViewState': {
'type': 'boolean',
'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the `#workbench.editor.sharedViewState#` setting to use the last known view state across all editor groups in case no previous view state was found for a editor group."),
'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the {0} setting to use the last known view state across all editor groups in case no previous view state was found for a editor group.", '`#workbench.editor.sharedViewState#`'),
'default': true,
'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE
},
@ -278,7 +278,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'type': 'number',
'default': 10,
'exclusiveMinimum': 0,
'markdownDescription': 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.")
'markdownDescription': localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the {0} setting to control this limit per editor group or across all groups.", '`#workbench.editor.limit.perEditorGroup#`')
},
'workbench.editor.limit.excludeDirty': {
'type': 'boolean',
@ -540,12 +540,12 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'window.titleSeparator': {
'type': 'string',
'default': isMacintosh ? ' \u2014 ' : ' - ',
'markdownDescription': localize("window.titleSeparator", "Separator used by `window.title`.")
'markdownDescription': localize("window.titleSeparator", "Separator used by {0}.", '`#window.title#`')
},
'window.commandCenter': {
type: 'boolean',
default: false,
markdownDescription: localize('window.commandCenter', "Show command launcher together with the window title. This setting only has an effect when `#window.titleBarStyle#` is set to `custom`.")
markdownDescription: localize('window.commandCenter', "Show command launcher together with the window title. This setting only has an effect when {0} is set to {1}.", '`#window.titleBarStyle#`', '`custom`')
},
'window.menuBarVisibility': {
'type': 'string',
@ -557,7 +557,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
localize('window.menuBarVisibility.toggle.mac', "Menu is hidden but can be displayed at the top of the window by executing the `Focus Application Menu` command.") :
localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed at the top of the window via the Alt key."),
localize('window.menuBarVisibility.hidden', "Menu is always hidden."),
localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the side bar. This value is ignored when `#window.titleBarStyle#` is `native`.")
localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the side bar. This value is ignored when {0} is {1}.", '`#window.titleBarStyle#`', '`native`')
],
'default': isWeb ? 'compact' : 'classic',
'scope': ConfigurationScope.APPLICATION,

View file

@ -11,6 +11,31 @@ import { EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRev
import { isEqual } from 'vs/base/common/resources';
import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
export interface IEditorCloseHandler {
/**
* If `true`, will call into the `confirm` method to ask for confirmation
* before closing the editor.
*/
showConfirm(): boolean;
/**
* Allows an editor to control what should happen when the editor
* (or a list of editor of the same kind) is being closed.
*
* By default a file specific dialog will open if the editor is
* dirty and not in the process of saving.
*
* If the editor is not dealing with files or another condition
* should be used besides dirty state, this method should be
* implemented to show a different dialog.
*
* @param editors if more than one editor is closed, will pass in
* each editor of the same kind to be able to show a combined dialog.
*/
confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
}
/**
* Editor inputs are lightweight objects that can be passed to the workbench API to open inside the editor part.
* Each editor input is mapped to an editor that is capable of opening it through the Platform facade.
@ -45,6 +70,12 @@ export abstract class EditorInput extends AbstractEditorInput {
private disposed: boolean = false;
/**
* Optional: subclasses can override to implement
* custom confirmation on close behavior.
*/
readonly closeHandler?: IEditorCloseHandler;
/**
* Unique type identifier for this input. Every editor input of the
* same class should share the same type identifier. The type identifier
@ -168,20 +199,6 @@ export abstract class EditorInput extends AbstractEditorInput {
return null;
}
/**
* Optional: if this method is implemented, allows an editor to
* control what should happen when the editor (or a list of editors
* of the same kind) is dirty and there is an intent to close it.
*
* By default a file specific dialog will open. If the editor is
* not dealing with files, this method should be implemented to
* show a different dialog.
*
* @param editors if more than one editor is closed, will pass in
* each editor of the same kind to be able to show a combined dialog.
*/
confirm?(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
/**
* Saves the editor. The provided groupId helps implementors
* to e.g. preserve view state of the editor and re-open it

View file

@ -403,8 +403,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
readonly dispose: () => void;
constructor(
@IOutlineService outlineService: IOutlineService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IOutlineService outlineService: IOutlineService
) {
const reg = outlineService.registerOutlineCreator(this);
this.dispose = () => reg.dispose();
@ -427,7 +426,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
return undefined;
}
const firstLoadBarrier = new Barrier();
const result = this._instantiationService.createInstance(DocumentSymbolsOutline, editor, target, firstLoadBarrier);
const result = editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor!, target, firstLoadBarrier));
await firstLoadBarrier.wait();
return result;
}

View file

@ -382,6 +382,7 @@ export class CommentController implements IEditorContribution {
this._commentingRangeDecorator.update(this.editor, []);
this._commentThreadRangeDecorator.update(this.editor, []);
dispose(this._commentWidgets);
this._commentWidgets = [];
}
}));

View file

@ -234,7 +234,7 @@ export class CommentsPanel extends ViewPane {
const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread;
if (threadToReveal && isCodeEditor(editor)) {
const controller = CommentController.get(editor);
controller?.revealCommentThread(threadToReveal, commentToReveal, false);
controller?.revealCommentThread(threadToReveal, commentToReveal, true);
}
return true;

View file

@ -723,7 +723,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration;
const config = await getConfig();
const configOrName = config ? Object.assign(deepClone(config), debugStartOptions?.config) : name;
await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true }, false);
await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true, saveBeforeStart: false });
}
});

View file

@ -312,7 +312,10 @@ export class DebugService implements IDebugService {
* main entry point
* properly manages compounds, checks for errors and handles the initializing state.
*/
async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart = !options?.parentSession): Promise<boolean> {
async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean> {
const saveBeforeStart = options?.saveBeforeStart ?? !options?.parentSession;
const message = options && options.noDebug ? nls.localize('runTrust', "Running executes build tasks and program code from your workspace.") : nls.localize('debugTrust', "Debugging executes build tasks and program code from your workspace.");
const trust = await this.workspaceTrustRequestService.requestWorkspaceTrust({ message });
if (!trust) {
@ -701,7 +704,10 @@ export class DebugService implements IDebugService {
}
async restartSession(session: IDebugSession, restartData?: any): Promise<any> {
await this.editorService.saveAll();
if (session.saveBeforeStart) {
await saveAllBeforeDebugStart(this.configurationService, this.editorService);
}
const isAutoRestart = !!restartData;
const runTasks: () => Promise<TaskRunResult> = async () => {

View file

@ -164,6 +164,10 @@ export class DebugSession implements IDebugSession {
return !!this._options.compact;
}
get saveBeforeStart(): boolean {
return this._options.saveBeforeStart ?? !this._options?.parentSession;
}
get compoundRoot(): DebugCompoundRoot | undefined {
return this._options.compoundRoot;
}

View file

@ -206,6 +206,7 @@ export interface IDebugSessionOptions {
simple?: boolean;
};
startedByUser?: boolean;
saveBeforeStart?: boolean;
}
export interface IDataBreakpointInfoResponse {
@ -296,6 +297,7 @@ export interface IDebugSession extends ITreeElement {
readonly subId: string | undefined;
readonly compact: boolean;
readonly compoundRoot: DebugCompoundRoot | undefined;
readonly saveBeforeStart: boolean;
readonly name: string;
readonly isSimpleUI: boolean;
readonly autoExpandLazyVariables: boolean;
@ -1088,7 +1090,7 @@ export interface IDebugService {
* Returns true if the start debugging was successful. For compound launches, all configurations have to start successfully for it to return success.
* On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false.
*/
startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart?: boolean): Promise<boolean>;
startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean>;
/**
* Restarts a session or creates a new one if there is no active session.

View file

@ -190,6 +190,10 @@ export class MockSession implements IDebugSession {
return undefined;
}
get saveBeforeStart(): boolean {
return true;
}
get isSimpleUI(): boolean {
return false;
}

View file

@ -185,7 +185,7 @@ configurationRegistry.registerConfiguration({
'files.autoGuessEncoding': {
'type': 'boolean',
'default': false,
'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only `#files.encoding#` is respected."),
'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only {0} is respected.", '`#files.encoding#`'),
'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE
},
'files.eol': {
@ -475,7 +475,7 @@ configurationRegistry.registerConfiguration({
},
'explorer.excludeGitIgnore': {
type: 'boolean',
markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `#files.exclude#`."),
markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to {0}.", '`#files.exclude#`'),
default: false,
scope: ConfigurationScope.RESOURCE
},
@ -487,7 +487,7 @@ configurationRegistry.registerConfiguration({
},
'explorer.fileNesting.expand': {
'type': 'boolean',
'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. `#explorer.fileNesting.enabled#` must be set for this to take effect."),
'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. {0} must be set for this to take effect.", '`#explorer.fileNesting.enabled#`'),
'default': true,
},
'explorer.fileNesting.patterns': {

View file

@ -3,31 +3,62 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { language } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { Language } from 'vs/base/common/platform';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks';
import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IProductService } from 'vs/platform/product/common/productService';
export class WebLocaleService implements ILocaleService {
declare readonly _serviceBrand: undefined;
async setLocale(languagePackItem: ILanguagePackItem): Promise<boolean> {
constructor(
@IDialogService private readonly dialogService: IDialogService,
@IHostService private readonly hostService: IHostService,
@IProductService private readonly productService: IProductService
) { }
async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
const locale = languagePackItem.id;
if (locale === language || (!locale && language === navigator.language)) {
return false;
if (locale === Language.value() || (!locale && Language.value() === navigator.language)) {
return;
}
if (locale) {
window.localStorage.setItem('vscode.nls.locale', locale);
} else {
window.localStorage.removeItem('vscode.nls.locale');
}
return true;
const restartDialog = await this.dialogService.confirm({
type: 'info',
message: localize('relaunchDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
detail: localize('relaunchDisplayLanguageDetail', "Press the reload button to refresh the page and set the display language to {0}.", languagePackItem.label),
primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
});
if (restartDialog.confirmed) {
this.hostService.restart();
}
}
async clearLocalePreference(): Promise<boolean> {
if (language === navigator.language) {
return false;
}
async clearLocalePreference(): Promise<void> {
window.localStorage.removeItem('vscode.nls.locale');
return true;
if (Language.value() === navigator.language) {
return;
}
const restartDialog = await this.dialogService.confirm({
type: 'info',
message: localize('clearDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
detail: localize('clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."),
primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
});
if (restartDialog.confirmed) {
this.hostService.restart();
}
}
}

View file

@ -5,9 +5,6 @@
import { localize } from 'vs/nls';
import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProductService } from 'vs/platform/product/common/productService';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
@ -15,8 +12,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks';
import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
const restart = localize('restart', "&&Restart");
export class ConfigureDisplayLanguageAction extends Action2 {
public static readonly ID = 'workbench.action.configureLocale';
public static readonly LABEL = localize('configureLocale', "Configure Display Language");
@ -34,9 +29,6 @@ export class ConfigureDisplayLanguageAction extends Action2 {
public async run(accessor: ServicesAccessor): Promise<void> {
const languagePackService: ILanguagePackService = accessor.get(ILanguagePackService);
const quickInputService: IQuickInputService = accessor.get(IQuickInputService);
const hostService: IHostService = accessor.get(IHostService);
const dialogService: IDialogService = accessor.get(IDialogService);
const productService: IProductService = accessor.get(IProductService);
const localeService: ILocaleService = accessor.get(ILocaleService);
const installedLanguages = await languagePackService.getInstalledLanguages();
@ -72,19 +64,7 @@ export class ConfigureDisplayLanguageAction extends Action2 {
disposables.add(qp.onDidAccept(async () => {
const selectedLanguage = qp.activeItems[0];
qp.hide();
if (await localeService.setLocale(selectedLanguage)) {
const restartDialog = await dialogService.confirm({
type: 'info',
message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."),
detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong),
primaryButton: restart
});
if (restartDialog.confirmed) {
hostService.restart();
}
}
await localeService.setLocale(selectedLanguage);
}));
qp.show();
@ -108,21 +88,6 @@ export class ClearDisplayLanguageAction extends Action2 {
public async run(accessor: ServicesAccessor): Promise<void> {
const localeService: ILocaleService = accessor.get(ILocaleService);
const dialogService: IDialogService = accessor.get(IDialogService);
const productService: IProductService = accessor.get(IProductService);
const hostService: IHostService = accessor.get(IHostService);
if (await localeService.clearLocalePreference()) {
const restartDialog = await dialogService.confirm({
type: 'info',
message: localize('relaunchAfterClearDisplayLanguageMessage', "A restart is required for the change in display language to take effect."),
detail: localize('relaunchAfterClearDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong),
primaryButton: restart
});
if (restartDialog.confirmed) {
hostService.restart();
}
}
await localeService.clearLocalePreference();
}
}

View file

@ -10,6 +10,6 @@ export const ILocaleService = createDecorator<ILocaleService>('localizationServi
export interface ILocaleService {
readonly _serviceBrand: undefined;
setLocale(languagePackItem: ILanguagePackItem): Promise<boolean>;
clearLocalePreference(): Promise<boolean>;
setLocale(languagePackItem: ILanguagePackItem): Promise<void>;
clearLocalePreference(): Promise<void>;
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { language } from 'vs/base/common/platform';
import { Language } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
@ -19,6 +19,9 @@ import { toAction } from 'vs/base/common/actions';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { stripComments } from 'vs/base/common/stripComments';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProductService } from 'vs/platform/product/common/productService';
export class NativeLocaleService implements ILocaleService {
_serviceBrand: undefined;
@ -32,7 +35,10 @@ export class NativeLocaleService implements ILocaleService {
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IProgressService private readonly progressService: IProgressService,
@ITextFileService private readonly textFileService: ITextFileService,
@IEditorService private readonly editorService: IEditorService
@IEditorService private readonly editorService: IEditorService,
@IDialogService private readonly dialogService: IDialogService,
@IHostService private readonly hostService: IHostService,
@IProductService private readonly productService: IProductService
) { }
private async validateLocaleFile(): Promise<boolean> {
@ -69,10 +75,10 @@ export class NativeLocaleService implements ILocaleService {
return true;
}
async setLocale(languagePackItem: ILanguagePackItem): Promise<boolean> {
async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
const locale = languagePackItem.id;
if (locale === language || (!locale && language === 'en')) {
return false;
if (locale === Language.value() || (!locale && Language.isDefaultVariant())) {
return;
}
const installedLanguages = await this.languagePackService.getInstalledLanguages();
try {
@ -87,7 +93,7 @@ export class NativeLocaleService implements ILocaleService {
// as of now, there are no 3rd party language packs available on the Marketplace.
const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar);
(viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`);
return false;
return;
}
await this.progressService.withProgress(
@ -102,22 +108,40 @@ export class NativeLocaleService implements ILocaleService {
);
}
return await this.writeLocaleValue(locale);
if (await this.writeLocaleValue(locale)) {
await this.showRestartDialog(languagePackItem.label);
}
} catch (err) {
this.notificationService.error(err);
return false;
}
}
async clearLocalePreference(): Promise<boolean> {
if (language === 'en') {
return false;
}
async clearLocalePreference(): Promise<void> {
try {
return await this.writeLocaleValue(undefined);
await this.writeLocaleValue(undefined);
if (!Language.isDefaultVariant()) {
await this.showRestartDialog('English');
}
} catch (err) {
this.notificationService.error(err);
return false;
}
}
private async showRestartDialog(languageName: string) {
const restartDialog = await this.dialogService.confirm({
type: 'info',
message: localize('restartDisplayLanguageMessage', "To change the display language, {0} needs to restart", this.productService.nameLong),
detail: localize(
'restartDisplayLanguageDetail',
"Press the restart button to restart {0} and set the display language to {1}.",
this.productService.nameLong,
languageName
),
primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic character'] }, "&&Restart"),
});
if (restartDialog.confirmed) {
this.hostService.restart();
}
}
}

View file

@ -8,11 +8,13 @@ import { registerAction2 } from 'vs/platform/actions/common/actions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout, OpenBaseFile } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { MergeEditorSerializer } from './mergeEditorSerializer';
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
@ -47,3 +49,8 @@ registerAction2(ToggleActiveConflictInput2);
registerAction2(CompareInput1WithBaseCommand);
registerAction2(CompareInput2WithBaseCommand);
Registry
.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored);

View file

@ -14,15 +14,13 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { assertType } from 'vs/base/common/types';
import { Event } from 'vs/base/common/event';
import { autorun } from 'vs/base/common/observable';
export class MergeEditorInputData {
constructor(
@ -39,7 +37,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
private _model?: MergeEditorModel;
private _outTextModel?: ITextFileEditorModel;
private _ignoreUnhandledConflictsForDirtyState?: true;
override closeHandler: MergeEditorCloseHandler | undefined;
constructor(
public readonly base: URI,
@ -48,8 +47,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
public readonly result: URI,
@IInstantiationService private readonly _instaService: IInstantiationService,
@ITextModelService private readonly _textModelService: ITextModelService,
@IDialogService private readonly _dialogService: IDialogService,
@IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService,
@IEditorService editorService: IEditorService,
@ITextFileService textFileService: ITextFileService,
@ILabelService labelService: ILabelService,
@ -118,6 +115,13 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
},
);
// set/unset the closeHandler whenever unhandled conflicts are detected
const closeHandler = this._instaService.createInstance(MergeEditorCloseHandler, this._model);
this._store.add(autorun('closeHandler', reader => {
const value = this._model!.hasUnhandledConflicts.read(reader);
this.closeHandler = value ? closeHandler : undefined;
}));
await this._model.onInitialized;
this._store.add(this._model);
@ -125,11 +129,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
this._store.add(input1);
this._store.add(input2);
this._store.add(result);
this._store.add(Event.fromObservable(this._model.hasUnhandledConflicts)(() => this._onDidChangeDirty.fire(undefined)));
}
this._ignoreUnhandledConflictsForDirtyState = undefined;
return this._model;
}
@ -146,66 +147,57 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
// ---- FileEditorInput
override isDirty(): boolean {
const textModelDirty = Boolean(this._outTextModel?.isDirty());
if (textModelDirty) {
// text model dirty -> 3wm is dirty
return true;
}
if (!this._ignoreUnhandledConflictsForDirtyState) {
// unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input
// to ignore unhandled conflicts for the dirty-state. This happens only
// after confirming to ignore unhandled changes
return Boolean(this._model && this._model.hasUnhandledConflicts.get());
}
return false;
return Boolean(this._outTextModel?.isDirty());
}
override async confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
setLanguageId(languageId: string, _setExplicitly?: boolean): void {
this._model?.setLanguageId(languageId);
}
const inputs: MergeEditorInput[] = [this];
if (editors) {
for (const { editor } of editors) {
if (editor instanceof MergeEditorInput) {
inputs.push(editor);
}
}
}
// implement get/set languageId
// implement get/set encoding
}
const inputsWithUnhandledConflicts = inputs
class MergeEditorCloseHandler implements IEditorCloseHandler {
private _ignoreUnhandledConflicts: boolean = false;
constructor(
private readonly _model: MergeEditorModel,
@IDialogService private readonly _dialogService: IDialogService,
) { }
showConfirm(): boolean {
// unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input
// to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes
return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get();
}
async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise<ConfirmResult> {
const handler: MergeEditorCloseHandler[] = [this];
editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler));
const inputsWithUnhandledConflicts = handler
.filter(input => input._model && input._model.hasUnhandledConflicts.get());
if (inputsWithUnhandledConflicts.length === 0) {
// shouldn't happen
return ConfirmResult.SAVE;
}
const actions: string[] = [];
const actions: string[] = [
localize('unhandledConflicts.ignore', "Continue with Conflicts"),
localize('unhandledConflicts.discard', "Discard Merge Changes"),
localize('unhandledConflicts.cancel', "Cancel"),
];
const options = {
cancelId: 0,
detail: inputs.length > 1
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputs.length)
cancelId: 2,
detail: handler.length > 1
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', handler.length)
: localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.')
};
const isAnyAutoSave = this._filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF;
if (!isAnyAutoSave) {
// manual-save: FYI and discard
actions.push(
localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0
localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1
localize('unhandledConflicts.manualSaveNoSave', "Don't Save"), // 2
);
} else {
// auto-save: only FYI
actions.push(
localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0
localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1
);
}
actions.push(localize('unhandledConflicts.cancel', "Cancel"));
options.cancelId = actions.length - 1;
const { choice } = await this._dialogService.show(
Severity.Info,
localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'), // 1
@ -220,8 +212,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
// save or revert: in both cases we tell the inputs to ignore unhandled conflicts
// for the dirty state computation.
for (const input of inputs) {
input._ignoreUnhandledConflictsForDirtyState = true;
for (const input of handler) {
input._ignoreUnhandledConflicts = true;
}
if (choice === 0) {
@ -230,7 +222,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
} else if (choice === 1) {
// discard: undo all changes and save original (pre-merge) state
for (const input of inputs) {
for (const input of handler) {
input._discardMergeChanges();
}
return ConfirmResult.SAVE;
@ -242,8 +234,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
}
private _discardMergeChanges(): void {
assertType(this._model !== undefined);
const chunks: string[] = [];
while (true) {
const chunk = this._model.resultSnapshot.read();
@ -254,11 +244,4 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
}
this._model.result.setValue(chunks.join());
}
setLanguageId(languageId: string, _setExplicitly?: boolean): void {
this._model?.setLanguageId(languageId);
}
// implement get/set languageId
// implement get/set encoding
}

View file

@ -11,12 +11,13 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Color } from 'vs/base/common/color';
import { BugIndicatingError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { autorunWithStore, IObservable } from 'vs/base/common/observable';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/mergeEditor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
@ -26,7 +27,7 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorOptions, ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
@ -35,7 +36,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor';
import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor';
import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
@ -46,7 +47,6 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v
import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import './colors';
import { InputCodeEditorView } from './editors/inputCodeEditorView';
@ -86,9 +86,9 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
private readonly _sessionDisposables = new DisposableStore();
private _grid!: Grid<IView>;
private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1));
private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2));
private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView));
private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1));
private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2));
private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView));
private readonly _layoutMode: MergeEditorLayout;
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
@ -103,7 +103,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
constructor(
@IInstantiationService private readonly instantiation: IInstantiationService,
@IInstantiationService instantiation: IInstantiationService,
@ILabelService private readonly _labelService: ILabelService,
@IMenuService private readonly _menuService: IMenuService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@ -115,7 +115,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IFileService fileService: IFileService,
@IEditorResolverService private readonly _editorResolverService: IEditorResolverService,
) {
super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService);
@ -186,7 +185,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
createAndFillInActionBarActions(toolbarMenu, { renderShortTitle: true, shouldForwardArgs: true }, actions);
if (actions.length > 0) {
const [first] = actions;
const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
const acceptBtn = this.instantiationService.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
toolbarMenuDisposables.add(acceptBtn.onClick(() => first.run(this.inputResultView.editor.getModel()?.uri)));
toolbarMenuDisposables.add(acceptBtn);
acceptBtn.render();
@ -296,7 +295,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
await super.setInput(input, options, context, token);
this._sessionDisposables.clear();
this._toggleEditorOverwrite(true);
const model = await input.resolve();
this._model = model;
@ -309,16 +307,17 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
this._ctxBaseResourceScheme.set(model.base.uri.scheme);
const viewState = this.loadEditorViewState(input, context);
this._applyViewState(viewState);
this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
if (!firstConflict) {
return;
}
this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
}));
if (viewState) {
this._applyViewState(viewState);
} else {
this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
if (!firstConflict) {
return;
}
this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
}));
}
this._sessionDisposables.add(autorunWithStore((reader, store) => {
@ -373,7 +372,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
super.clearInput();
this._sessionDisposables.clear();
this._toggleEditorOverwrite(false);
for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) {
editor.setModel(null);
@ -405,39 +403,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
this._ctxIsMergeEditor.set(visible);
this._toggleEditorOverwrite(visible);
}
private readonly _editorOverrideHandle = this._store.add(new MutableDisposable());
private _toggleEditorOverwrite(haveIt: boolean) {
if (!haveIt) {
this._editorOverrideHandle.clear();
return;
}
// this is RATHER UGLY. I dynamically register an editor for THIS (editor,input) so that
// navigating within the merge editor works, e.g navigating from the outline or breakcrumps
// or revealing a definition, reference etc
// TODO@jrieken @bpasero @lramos15
const input = this.input;
if (input instanceof MergeEditorInput) {
this._editorOverrideHandle.value = this._editorResolverService.registerEditor(
`${input.result.scheme}:${input.result.fsPath}`,
{
id: `${this.getId()}/fake`,
label: this.input?.getName()!,
priority: RegisteredEditorPriority.exclusive
},
{},
(candidate): EditorInputWithOptions => {
const resource = EditorResourceAccessor.getCanonicalUri(candidate);
if (!isEqual(resource, this.model?.result.uri)) {
throw new Error(`Expected to be called WITH ${input.result.toString()}`);
}
return { editor: input };
}
);
}
}
// ---- interact with "outside world" via`getControl`, `scopedContextKeyService`: we only expose the result-editor keep the others internal
@ -506,6 +471,37 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
}
export class MergeEditorOpenHandlerContribution extends Disposable {
constructor(
@IEditorService private readonly _editorService: IEditorService,
@ICodeEditorService codeEditorService: ICodeEditorService,
) {
super();
this._store.add(codeEditorService.registerCodeEditorOpenHandler(this.openCodeEditorFromMergeEditor.bind(this)));
}
private async openCodeEditorFromMergeEditor(input: ITextResourceEditorInput, _source: ICodeEditor | null, sideBySide?: boolean | undefined): Promise<ICodeEditor | null> {
const activePane = this._editorService.activeEditorPane;
if (!sideBySide
&& input.options
&& activePane instanceof MergeEditor
&& activePane.getControl()
&& activePane.input instanceof MergeEditorInput
&& isEqual(input.resource, activePane.input.result)
) {
// Special: stay inside the merge editor when it is active and when the input
// targets the result editor of the merge editor.
const targetEditor = <ICodeEditor>activePane.getControl()!;
applyTextEditorOptions(input.options, targetEditor, ScrollType.Smooth);
return targetEditor;
}
// cannot handle this
return null;
}
}
type IMergeEditorViewState = ICodeEditorViewState & {
readonly input1State?: ICodeEditorViewState;
readonly input2State?: ICodeEditorViewState;

View file

@ -881,7 +881,7 @@ configurationRegistry.registerConfiguration({
tags: ['notebookLayout']
},
[NotebookSetting.markupFontSize]: {
markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used."),
markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to {0}, 120% of {1} is used.", '`0`', '`#editor.fontSize#`'),
type: 'number',
default: 0,
tags: ['notebookLayout']
@ -900,13 +900,13 @@ configurationRegistry.registerConfiguration({
tags: ['notebookLayout']
},
[NotebookSetting.outputFontSize]: {
markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used."),
markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to {0}, {1} is used.", '`0`', '`#editor.fontSize#`'),
type: 'number',
default: 0,
tags: ['notebookLayout']
},
[NotebookSetting.outputFontFamily]: {
markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used."),
markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the {0} is used.", '`#editor.fontFamily#`'),
type: 'string',
tags: ['notebookLayout']
},

View file

@ -351,7 +351,7 @@
font-style: italic;
}
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides:hover,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides.with-custom-hover:hover,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-ignored:hover,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-default-overridden:hover {
text-decoration: underline;

View file

@ -51,12 +51,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
this.indicatorsContainerElement = DOM.append(container, $('.misc-label'));
this.indicatorsContainerElement.style.display = 'inline';
const scopeOverridesIndicator = this.createScopeOverridesIndicator();
this.scopeOverridesElement = scopeOverridesIndicator.element;
this.scopeOverridesLabel = scopeOverridesIndicator.label;
this.syncIgnoredElement = this.createSyncIgnoredElement();
this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
this.hoverDelegate = {
showHover: (options: IHoverDelegateOptions, focus?: boolean) => {
return hoverService.showHover(options, focus);
@ -65,6 +59,12 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
delay: configurationService.getValue<number>('workbench.hover.delay'),
placement: 'element'
};
const scopeOverridesIndicator = this.createScopeOverridesIndicator();
this.scopeOverridesElement = scopeOverridesIndicator.element;
this.scopeOverridesLabel = scopeOverridesIndicator.label;
this.syncIgnoredElement = this.createSyncIgnoredElement();
this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
}
private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } {
@ -139,6 +139,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
// Render inline if we have the flag and there are scope overrides to render,
// or if there is only one scope override to render and no language overrides.
this.scopeOverridesElement.style.display = 'inline';
this.scopeOverridesElement.classList.remove('with-custom-hover');
this.hover?.dispose();
// Just show all the text in the label.
@ -170,6 +171,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
// show the text in a custom hover only if
// the feature flag isn't on.
this.scopeOverridesElement.style.display = 'inline';
this.scopeOverridesElement.classList.add('with-custom-hover');
const scopeOverridesLabelText = element.isConfigured ?
localize('alsoConfiguredElsewhere', "Also modified elsewhere") :
localize('configuredElsewhere', "Modified elsewhere");

View file

@ -353,7 +353,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
},
'remote.autoForwardPortsSource': {
type: 'string',
markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when `remote.autoForwardPorts` is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect."),
markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'),
enum: ['process', 'output'],
enumDescriptions: [
localize('remote.autoForwardPortsSource.process', "Ports will be automatically forwarded when discovered by watching for processes that are started and include a port."),
@ -463,7 +463,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
},
defaultSnippets: [{ body: { onAutoForward: 'ignore' } }],
markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```"),
markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting {0}. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", '`#remote.portsAttributes#`'),
additionalProperties: false
},
'remote.localPortHost': {

View file

@ -996,7 +996,7 @@ configurationRegistry.registerConfiguration({
'search.searchOnTypeDebouncePeriod': {
type: 'number',
default: 300,
markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.")
markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When {0} is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when {0} is disabled.", '`#search.searchOnType#`')
},
'search.searchEditor.doubleClickBehaviour': {
type: 'string',

View file

@ -53,7 +53,9 @@ import {
ITaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind,
TaskSorter, ITaskIdentifier, TASK_RUNNING_STATE, TaskRunSource,
KeyedTaskIdentifier as KeyedTaskIdentifier, TaskDefinition, RuntimeType,
USER_TASKS_GROUP_KEY
USER_TASKS_GROUP_KEY,
TaskSettingId,
TasksSchemaProperties
} from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates';
@ -1082,7 +1084,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}).then((value) => {
if (runSource === TaskRunSource.User) {
this.getWorkspaceTasks().then(workspaceTasks => {
RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, workspaceTasks);
RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, this._configurationService, workspaceTasks);
});
}
return value;
@ -1093,7 +1095,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
private _isProvideTasksEnabled(): boolean {
const settingValue = this._configurationService.getValue('task.autoDetect');
const settingValue = this._configurationService.getValue(TaskSettingId.AutoDetect);
return settingValue === 'on';
}
@ -1688,7 +1690,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
Prompt = 'prompt'
}
const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue('task.saveBeforeRun');
const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue(TaskSettingId.SaveBeforeRun);
if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Never) {
return false;
@ -3523,11 +3525,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
const configTasks: (TaskConfig.ICustomTask | TaskConfig.IConfiguringTask)[] = [];
const suppressTaskName = !!this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri });
const suppressTaskName = !!this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri });
const globalConfig = {
windows: <ICommandUpgrade>this._configurationService.getValue('tasks.windows', { resource: folder.uri }),
osx: <ICommandUpgrade>this._configurationService.getValue('tasks.osx', { resource: folder.uri }),
linux: <ICommandUpgrade>this._configurationService.getValue('tasks.linux', { resource: folder.uri })
windows: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Windows, { resource: folder.uri }),
osx: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Osx, { resource: folder.uri }),
linux: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Linux, { resource: folder.uri })
};
tasks.get(folder).forEach(task => {
const configTask = this._upgradeTask(task, suppressTaskName, globalConfig);
@ -3539,14 +3541,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
this._workspaceTasksPromise = undefined;
await this._writeConfiguration(folder, 'tasks.tasks', configTasks);
await this._writeConfiguration(folder, 'tasks.version', '2.0.0');
if (this._configurationService.getValue('tasks.showOutput', { resource: folder.uri })) {
await this._configurationService.updateValue('tasks.showOutput', undefined, { resource: folder.uri });
if (this._configurationService.getValue(TasksSchemaProperties.ShowOutput, { resource: folder.uri })) {
await this._configurationService.updateValue(TasksSchemaProperties.ShowOutput, undefined, { resource: folder.uri });
}
if (this._configurationService.getValue('tasks.isShellCommand', { resource: folder.uri })) {
await this._configurationService.updateValue('tasks.isShellCommand', undefined, { resource: folder.uri });
if (this._configurationService.getValue(TasksSchemaProperties.IsShellCommand, { resource: folder.uri })) {
await this._configurationService.updateValue(TasksSchemaProperties.IsShellCommand, undefined, { resource: folder.uri });
}
if (this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri })) {
await this._configurationService.updateValue('tasks.suppressTaskName', undefined, { resource: folder.uri });
if (this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri })) {
await this._configurationService.updateValue(TasksSchemaProperties.SuppressTaskName, undefined, { resource: folder.uri });
}
}
this._updateSetup();

View file

@ -15,18 +15,19 @@ import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/commo
import { Action2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic';
const HAS_PROMPTED_FOR_AUTOMATIC_TASKS = 'task.hasPromptedForAutomaticTasks';
const ALLOW_AUTOMATIC_TASKS = 'task.allowAutomaticTasks';
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
constructor(
@ITaskService private readonly _taskService: ITaskService,
@IStorageService private readonly _storageService: IStorageService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService private readonly _logService: ILogService) {
super();
@ -42,7 +43,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
this._logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.');
const isFolderAutomaticAllowed = this._storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
const isFolderAutomaticAllowed = this._configurationService.getValue(ALLOW_AUTOMATIC_TASKS) !== 'off';
await this._workspaceTrustManagementService.workspaceTrustInitialized;
const isWorkspaceTrusted = this._workspaceTrustManagementService.isWorkspaceTrusted();
// Only run if allowed. Prompting for permission occurs when a user first tries to run a task.
@ -128,30 +129,33 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustManagementService: IWorkspaceTrustManagementService,
openerService: IOpenerService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>) {
openerService: IOpenerService, configurationService: IConfigurationService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>) {
const isWorkspaceTrusted = workspaceTrustManagementService.isWorkspaceTrusted;
if (!isWorkspaceTrusted) {
return;
}
const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
if (isFolderAutomaticAllowed !== undefined) {
if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'off') {
return;
}
const hasShownPromptForAutomaticTasks = storageService.getBoolean(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, StorageScope.WORKSPACE, undefined);
const { tasks, taskNames, locations } = RunAutomaticTasks._findAutoTasks(taskService, workspaceTaskResult);
if (taskNames.length > 0) {
// We have automatic tasks, prompt to allow.
this._showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => {
if (allow) {
RunAutomaticTasks._runTasks(taskService, tasks);
}
});
if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'on') {
RunAutomaticTasks._runTasks(taskService, tasks);
} else if (!hasShownPromptForAutomaticTasks) {
// We have automatic tasks, prompt to allow.
this._showPrompt(notificationService, storageService, openerService, configurationService, taskNames, locations).then(allow => {
if (allow) {
RunAutomaticTasks._runTasks(taskService, tasks);
}
});
}
}
}
private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService,
openerService: IOpenerService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
private static _showPrompt(notificationService: INotificationService, storageService: IStorageService,
openerService: IOpenerService, configurationService: IConfigurationService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
return new Promise<boolean>(resolve => {
notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic',
"This workspace has tasks ({0}) defined ({1}) that run automatically when you open this workspace. Do you allow automatic tasks to run when you open this workspace?",
@ -162,14 +166,15 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
label: nls.localize('allow', "Allow and run"),
run: () => {
resolve(true);
storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, true, ConfigurationTarget.WORKSPACE);
}
},
{
label: nls.localize('disallow', "Disallow"),
run: () => {
resolve(false);
storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE, StorageTarget.MACHINE);
configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, false, ConfigurationTarget.WORKSPACE);
}
},
{
@ -182,9 +187,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
}]
);
storageService.store(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
});
}
}
export class ManageAutomaticTaskRunning extends Action2 {
@ -202,14 +207,13 @@ export class ManageAutomaticTaskRunning extends Action2 {
public async run(accessor: ServicesAccessor): Promise<any> {
const quickInputService = accessor.get(IQuickInputService);
const storageService = accessor.get(IStorageService);
const configurationService = accessor.get(IConfigurationService);
const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") };
const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") };
const value = await quickInputService.pick([allowItem, disallowItem], { canPickMany: false });
if (!value) {
return;
}
storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE, StorageTarget.MACHINE);
configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, value === allowItem, ConfigurationTarget.WORKSPACE);
}
}

View file

@ -20,7 +20,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output';
import { ITaskEvent, TaskEventKind, TaskGroup, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
@ -431,7 +431,7 @@ configurationRegistry.registerConfiguration({
title: nls.localize('tasksConfigurationTitle', "Tasks"),
type: 'object',
properties: {
'task.problemMatchers.neverPrompt': {
[TaskSettingId.ProblemMatchersNeverPrompt]: {
markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never prompt, or use a dictionary of task types to turn off prompting only for specific task types."),
'oneOf': [
{
@ -453,13 +453,13 @@ configurationRegistry.registerConfiguration({
],
default: false
},
'task.autoDetect': {
[TaskSettingId.AutoDetect]: {
markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions may also provide settings that disable auto detection."),
type: 'string',
enum: ['on', 'off'],
default: 'on'
},
'task.slowProviderWarning': {
[TaskSettingId.SlowProviderWarning]: {
markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"),
'oneOf': [
{
@ -476,32 +476,44 @@ configurationRegistry.registerConfiguration({
],
default: true
},
'task.quickOpen.history': {
[TaskSettingId.QuickOpenHistory]: {
markdownDescription: nls.localize('task.quickOpen.history', "Controls the number of recent items tracked in task quick open dialog."),
type: 'number',
default: 30, minimum: 0, maximum: 30
},
'task.quickOpen.detail': {
[TaskSettingId.QuickOpenDetail]: {
markdownDescription: nls.localize('task.quickOpen.detail', "Controls whether to show the task detail for tasks that have a detail in task quick picks, such as Run Task."),
type: 'boolean',
default: true
},
'task.quickOpen.skip': {
[TaskSettingId.QuickOpenSkip]: {
type: 'boolean',
description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."),
default: false
},
'task.quickOpen.showAll': {
[TaskSettingId.QuickOpenShowAll]: {
type: 'boolean',
description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."),
default: false
},
'task.showDecorations': {
[TaskSettingId.AllowAutomaticTasks]: {
type: 'string',
enum: ['on', 'auto', 'off'],
enumDescriptions: [
nls.localize('ttask.allowAutomaticTasks.on', "Always"),
nls.localize('task.allowAutomaticTasks.auto', "Prompt for permission for each folder"),
nls.localize('task.allowAutomaticTasks.off', "Never"),
],
description: nls.localize('task.allowAutomaticTasks', "Enable automatic tasks in the folder."),
default: 'auto',
restricted: true
},
[TaskSettingId.ShowDecorations]: {
type: 'boolean',
description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."),
default: true
},
'task.saveBeforeRun': {
[TaskSettingId.SaveBeforeRun]: {
markdownDescription: nls.localize(
'task.saveBeforeRun',
'Save all dirty editors before running a task.'

View file

@ -31,7 +31,7 @@ import { IOutputService } from 'vs/workbench/services/output/common/output';
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors';
import {
Task, CustomTask, ContributedTask, RevealKind, CommandOptions, IShellConfiguration, RuntimeType, PanelKind,
TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent
TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent, TaskSettingId
} from 'vs/workbench/contrib/tasks/common/tasks';
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
@ -209,10 +209,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
private readonly _onDidStateChange: Emitter<ITaskEvent>;
get taskShellIntegrationStartSequence(): string {
return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : '';
return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : '';
}
get taskShellIntegrationOutputSequence(): string {
return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : '';
return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : '';
}
constructor(
@ -1157,7 +1157,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
}
} else {
shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
shellLaunchConfig.initialText = {
text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
trailingNewLine: false
};
}
} else {
const commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined;
@ -1197,7 +1200,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
}
} else {
shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
shellLaunchConfig.initialText = {
text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
trailingNewLine: false
};
}
}

View file

@ -1190,6 +1190,30 @@ export namespace KeyedTaskIdentifier {
}
}
export const enum TaskSettingId {
AutoDetect = 'task.autoDetect',
SaveBeforeRun = 'task.saveBeforeRun',
ShowDecorations = 'task.showDecorations',
ProblemMatchersNeverPrompt = 'task.problemMatchers.neverPrompt',
SlowProviderWarning = 'task.slowProviderWarning',
QuickOpenHistory = 'task.quickOpen.history',
QuickOpenDetail = 'task.quickOpen.detail',
QuickOpenSkip = 'task.quickOpen.skip',
QuickOpenShowAll = 'task.quickOpen.showAll',
AllowAutomaticTasks = 'task.allowAutomaticTasks'
}
export const enum TasksSchemaProperties {
Tasks = 'tasks',
SuppressTaskName = 'tasks.suppressTaskName',
Windows = 'tasks.windows',
Osx = 'tasks.osx',
Linux = 'tasks.linux',
ShowOutput = 'tasks.showOutput',
IsShellCommand = 'tasks.isShellCommand',
ServiceTestSetting = 'tasks.service.testSetting',
}
export namespace TaskDefinition {
export function createTaskIdentifier(external: ITaskIdentifier, reporter: { error(message: string): void }): KeyedTaskIdentifier | undefined {
const definition = TaskDefinitionRegistry.get(external.type);

View file

@ -39,13 +39,6 @@ if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then
builtin return
fi
# Disable shell integration if HISTCONTROL is set to erase duplicate entries as the exit code
# reporting relies on the duplicates existing
if [[ "$HISTCONTROL" =~ .*erasedups.* ]]; then
builtin unset VSCODE_SHELL_INTEGRATION
builtin return
fi
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
builtin return
fi
@ -56,7 +49,7 @@ __vsc_original_PS2="$PS2"
__vsc_custom_PS1=""
__vsc_custom_PS2=""
__vsc_in_command_execution="1"
__vsc_last_history_id=$(history 1 | awk '{print $1;}')
__vsc_current_command=""
__vsc_prompt_start() {
builtin printf "\033]633;A\007"
@ -72,6 +65,7 @@ __vsc_update_cwd() {
__vsc_command_output_start() {
builtin printf "\033]633;C\007"
builtin printf "\033]633;E;$__vsc_current_command\007"
}
__vsc_continuation_start() {
@ -83,12 +77,10 @@ __vsc_continuation_end() {
}
__vsc_command_complete() {
local __vsc_history_id=$(builtin history 1 | awk '{print $1;}')
if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
if [ "$__vsc_current_command" = "" ]; then
builtin printf "\033]633;D\007"
else
builtin printf "\033]633;D;%s\007" "$__vsc_status"
__vsc_last_history_id=$__vsc_history_id
fi
__vsc_update_cwd
}
@ -113,6 +105,7 @@ __vsc_update_prompt() {
__vsc_precmd() {
__vsc_command_complete "$__vsc_status"
__vsc_current_command=""
__vsc_update_prompt
}
@ -120,6 +113,11 @@ __vsc_preexec() {
if [ "$__vsc_in_command_execution" = "0" ]; then
__vsc_initialized=1
__vsc_in_command_execution="1"
if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then
__vsc_current_command=$BASH_COMMAND
else
__vsc_current_command=""
fi
__vsc_command_output_start
fi
}

View file

@ -33,9 +33,8 @@ if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
builtin return
fi
__vsc_initialized="0"
__vsc_in_command_execution="1"
__vsc_last_history_id=0
__vsc_current_command=""
__vsc_prompt_start() {
builtin printf "\033]633;A\007"
@ -51,6 +50,7 @@ __vsc_update_cwd() {
__vsc_command_output_start() {
builtin printf "\033]633;C\007"
builtin printf "\033]633;E;$__vsc_current_command\007"
}
__vsc_continuation_start() {
@ -70,17 +70,10 @@ __vsc_right_prompt_end() {
}
__vsc_command_complete() {
builtin local __vsc_history_id=$(builtin history | tail -n1 | awk '{print $1;}')
# Don't write the command complete sequence for the first prompt without an associated command
if [[ "$__vsc_initialized" == "1" ]]; then
if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
builtin printf "\033]633;D\007"
else
builtin printf "\033]633;D;%s\007" "$__vsc_status"
__vsc_last_history_id=$__vsc_history_id
fi
else
if [[ "$__vsc_current_command" == "" ]]; then
builtin printf "\033]633;D\007"
else
builtin printf "\033]633;D;%s\007" "$__vsc_status"
fi
__vsc_update_cwd
}
@ -112,6 +105,7 @@ __vsc_precmd() {
fi
__vsc_command_complete "$__vsc_status"
__vsc_current_command=""
# in command execution
if [ -n "$__vsc_in_command_execution" ]; then
@ -125,8 +119,8 @@ __vsc_preexec() {
if [ -n "$RPROMPT" ]; then
RPROMPT="$__vsc_prior_rprompt"
fi
__vsc_initialized="1"
__vsc_in_command_execution="1"
__vsc_current_command=$1
__vsc_command_output_start
}
add-zsh-hook precmd __vsc_precmd

View file

@ -2127,10 +2127,11 @@ export function registerTerminalActions() {
title: { value: localize('workbench.action.terminal.sizeToContentWidth', "Toggle Size to Content Width"), original: 'Toggle Size to Content Width' },
f1: true,
category,
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen, TerminalContextKeys.focus),
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen),
keybinding: {
primary: KeyMod.Alt | KeyCode.KeyZ,
weight: KeybindingWeight.WorkbenchContrib
weight: KeybindingWeight.WorkbenchContrib,
when: TerminalContextKeys.focus
}
});
}
@ -2138,12 +2139,13 @@ export function registerTerminalActions() {
await accessor.get(ITerminalService).doWithActiveInstance(t => t.toggleSizeToContentWidth());
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.SizeToContentWidthInstance,
title: terminalStrings.toggleSizeToContentWidth,
f1: true,
f1: false,
category,
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus)
});

View file

@ -9,7 +9,7 @@ import { dispose, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { EditorInputCapabilities, IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
import { ITerminalInstance, ITerminalInstanceService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal';
import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -23,21 +23,22 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin
import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { Emitter } from 'vs/base/common/event';
export class TerminalEditorInput extends EditorInput {
protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
readonly onDidRequestAttach = this._onDidRequestAttach.event;
export class TerminalEditorInput extends EditorInput implements IEditorCloseHandler {
static readonly ID = 'workbench.editors.terminal';
override readonly closeHandler = this;
private _isDetached = false;
private _isShuttingDown = false;
private _isReverted = false;
private _copyLaunchConfig?: IShellLaunchConfig;
private _terminalEditorFocusContextKey: IContextKey<boolean>;
private _group: IEditorGroup | undefined;
protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
readonly onDidRequestAttach = this._onDidRequestAttach.event;
setGroup(group: IEditorGroup | undefined) {
this._group = group;
}
@ -64,13 +65,6 @@ export class TerminalEditorInput extends EditorInput {
}
this._terminalInstance = instance;
this._setupInstanceListeners();
// Refresh dirty state when the confirm on kill setting is changed
this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
this._onDidChangeDirty.fire();
}
});
}
override copy(): EditorInput {
@ -95,7 +89,7 @@ export class TerminalEditorInput extends EditorInput {
return this._isDetached ? undefined : this._terminalInstance;
}
override isDirty(): boolean {
showConfirm(): boolean {
if (this._isReverted) {
return false;
}
@ -106,7 +100,7 @@ export class TerminalEditorInput extends EditorInput {
return false;
}
override async confirm(terminals?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
async confirm(terminals?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
const { choice } = await this._dialogService.show(
Severity.Warning,
localize('confirmDirtyTerminal.message', "Do you want to terminate running processes?"),
@ -148,12 +142,6 @@ export class TerminalEditorInput extends EditorInput {
this._terminalEditorFocusContextKey = TerminalContextKeys.editorFocus.bindTo(_contextKeyService);
// Refresh dirty state when the confirm on kill setting is changed
this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
this._onDidChangeDirty.fire();
}
});
if (_terminalInstance) {
this._setupInstanceListeners();
}
@ -178,7 +166,6 @@ export class TerminalEditorInput extends EditorInput {
instance.onIconChanged(() => this._onDidChangeLabel.fire()),
instance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true)),
instance.onDidBlur(() => this._terminalEditorFocusContextKey.reset()),
instance.onDidChangeHasChildProcesses(() => this._onDidChangeDirty.fire()),
instance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire())
];

View file

@ -550,7 +550,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _getIcon(): TerminalIcon | undefined {
if (!this._icon) {
this._icon = this._processManager.processState >= ProcessState.Launching
? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId))
? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon))
: undefined;
}
return this._icon;
@ -698,7 +698,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Write initial text, deferring onLineFeed listener when applicable to avoid firing
// onLineData events containing initialText
if (this._shellLaunchConfig.initialText) {
this.xterm.raw.writeln(this._shellLaunchConfig.initialText, () => {
this._writeInitialText(this.xterm, () => {
lineDataEventAddon.onLineData(e => this._onLineData.fire(e));
});
} else {
@ -821,7 +821,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._linkManager.openRecentLink(type);
}
async runRecent(type: 'command' | 'cwd'): Promise<void> {
async runRecent(type: 'command' | 'cwd', filterMode?: 'fuzzy' | 'contiguous', value?: string): Promise<void> {
if (!this.xterm) {
return;
}
@ -968,42 +968,61 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider);
const quickPick = this._quickInputService.createQuickPick();
quickPick.items = items;
const originalItems = items;
quickPick.items = [...originalItems];
quickPick.sortByLabel = false;
quickPick.placeholder = placeholder;
return new Promise<void>(r => {
quickPick.onDidTriggerItemButton(async e => {
if (e.button === removeFromCommandHistoryButton) {
if (type === 'command') {
this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
} else {
this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
}
quickPick.customButton = true;
quickPick.matchOnLabelMode = filterMode || 'contiguous';
if (filterMode === 'fuzzy') {
quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search');
quickPick.onDidCustom(() => {
quickPick.hide();
this.runRecent(type, 'contiguous', quickPick.value);
});
} else {
quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search');
quickPick.onDidCustom(() => {
quickPick.hide();
this.runRecent(type, 'fuzzy', quickPick.value);
});
}
quickPick.onDidTriggerItemButton(async e => {
if (e.button === removeFromCommandHistoryButton) {
if (type === 'command') {
this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
} else {
const selectedCommand = (e.item as Item).command;
const output = selectedCommand?.getOutput();
if (output && selectedCommand?.command) {
const textContent = await outputProvider.provideTextContent(URI.from(
{
scheme: TerminalOutputProvider.scheme,
path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
fragment: output,
query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
}));
if (textContent) {
await this._editorService.openEditor({
resource: textContent.uri
});
}
this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
}
} else {
const selectedCommand = (e.item as Item).command;
const output = selectedCommand?.getOutput();
if (output && selectedCommand?.command) {
const textContent = await outputProvider.provideTextContent(URI.from(
{
scheme: TerminalOutputProvider.scheme,
path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
fragment: output,
query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
}));
if (textContent) {
await this._editorService.openEditor({
resource: textContent.uri
});
}
}
quickPick.hide();
});
quickPick.onDidAccept(() => {
const result = quickPick.activeItems[0];
this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
quickPick.hide();
});
}
quickPick.hide();
});
quickPick.onDidAccept(() => {
const result = quickPick.activeItems[0];
this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
quickPick.hide();
});
if (value) {
quickPick.value = value;
}
return new Promise<void>(r => {
quickPick.show();
quickPick.onDidHide(() => r());
});
@ -1802,29 +1821,49 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
private _writeInitialText(xterm: XtermTerminal, callback?: () => void): void {
if (!this._shellLaunchConfig.initialText) {
callback?.();
return;
}
const text = typeof this._shellLaunchConfig.initialText === 'string'
? this._shellLaunchConfig.initialText
: this._shellLaunchConfig.initialText?.text;
if (typeof this._shellLaunchConfig.initialText === 'string') {
xterm.raw.writeln(text, callback);
} else {
if (this._shellLaunchConfig.initialText.trailingNewLine) {
xterm.raw.writeln(text, callback);
} else {
xterm.raw.write(text, callback);
}
}
}
async reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): Promise<void> {
// Unsubscribe any key listener we may have.
this._pressAnyKeyToCloseListener?.dispose();
this._pressAnyKeyToCloseListener = undefined;
if (this.xterm) {
const xterm = this.xterm;
if (xterm) {
if (!reset) {
// Ensure new processes' output starts at start of new line
await new Promise<void>(r => this.xterm!.raw.write('\n\x1b[G', r));
await new Promise<void>(r => xterm.raw.write('\n\x1b[G', r));
}
// Print initialText if specified
if (shell.initialText) {
await new Promise<void>(r => this.xterm!.raw.writeln(shell.initialText!, r));
await new Promise<void>(r => this._writeInitialText(xterm, r));
}
// Clean up waitOnExit state
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
this.xterm.raw.options.disableStdin = false;
xterm.raw.options.disableStdin = false;
this._isExiting = false;
}
if (reset) {
this.xterm.clearDecorations();
xterm.clearDecorations();
}
}

View file

@ -417,6 +417,7 @@ export function setupTerminalMenus(): void {
order: 2,
when: ContextKeyExpr.and(
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
ContextKeyExpr.or(
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
ContextKeyExpr.and(
@ -451,6 +452,7 @@ export function setupTerminalMenus(): void {
order: 3,
when: ContextKeyExpr.and(
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
ContextKeyExpr.or(
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
ContextKeyExpr.and(

View file

@ -117,7 +117,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
getDefaultIcon(): TerminalIcon & ThemeIcon {
return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId)) || Codicon.terminal;
return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) || Codicon.terminal;
}
async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise<void> {
@ -157,7 +157,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
// Apply the color
shellLaunchConfig.color = shellLaunchConfig.color
|| resolvedProfile.color
|| this._configurationService.getValue(TerminalSettingId.TabsDefaultIconColor);
|| this._configurationService.getValue(TerminalSettingId.TabsDefaultColor);
// Resolve useShellEnvironment based on the setting if it's not set
if (shellLaunchConfig.useShellEnvironment === undefined) {

View file

@ -40,12 +40,12 @@ const terminalConfiguration: IConfigurationNode = {
type: 'boolean',
default: false
},
[TerminalSettingId.TabsDefaultIconColor]: {
description: localize('terminal.integrated.tabs.defaultIconColor', "A theme color ID to associate with terminals by default."),
[TerminalSettingId.TabsDefaultColor]: {
description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminal icons by default."),
...terminalColorSchema
},
[TerminalSettingId.TabsDefaultIconId]: {
description: localize('terminal.integrated.tabs.defaultIconId', "A codicon ID to associate with terminals by default."),
[TerminalSettingId.TabsDefaultIcon]: {
description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminal icons by default."),
...terminalIconSchema,
default: Codicon.terminal.id,
},

View file

@ -56,6 +56,7 @@ import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
export class DesktopMain extends Disposable {
@ -184,6 +185,9 @@ export class DesktopMain extends Disposable {
if (logService.getLevel() === LogLevel.Trace) {
logService.trace('workbench#open(): with configuration', safeStringify(this.configuration));
}
if (process.sandboxed) {
logService.info('Electron sandbox mode is enabled!');
}
// Shared Process
const sharedProcessService = new SharedProcessService(this.configuration.windowId, logService);

View file

@ -50,6 +50,7 @@ import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks';
function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier {
return {
@ -1096,7 +1097,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
test('update workspace configuration', () => {
return testObject.updateValue('tasks.service.testSetting', 'value', ConfigurationTarget.WORKSPACE)
.then(() => assert.strictEqual(testObject.getValue('tasks.service.testSetting'), 'value'));
.then(() => assert.strictEqual(testObject.getValue(TasksSchemaProperties.ServiceTestSetting), 'value'));
});
test('update resource configuration', () => {
@ -1150,7 +1151,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
test('update tasks configuration', () => {
return testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, ConfigurationTarget.WORKSPACE)
.then(() => assert.deepStrictEqual(testObject.getValue('tasks'), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }));
.then(() => assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }));
});
test('update user configuration should trigger change event before promise is resolve', () => {
@ -1994,7 +1995,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
};
await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['tasks'], value: expectedTasksConfiguration }], true);
await testObject.reloadConfiguration();
const actual = testObject.getValue('tasks');
const actual = testObject.getValue(TasksSchemaProperties.Tasks);
assert.deepStrictEqual(actual, expectedTasksConfiguration);
});
@ -2119,7 +2120,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
test('update tasks configuration in a folder', async () => {
const workspace = workspaceContextService.getWorkspace();
await testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER);
assert.deepStrictEqual(testObject.getValue('tasks', { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] });
assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks, { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] });
});
test('update launch configuration in a workspace', async () => {
@ -2132,7 +2133,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
const workspace = workspaceContextService.getWorkspace();
const tasks = { 'version': '2.0.0', tasks: [{ 'label': 'myTask' }] };
await testObject.updateValue('tasks', tasks, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true);
assert.deepStrictEqual(testObject.getValue('tasks'), tasks);
assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks), tasks);
});
test('configuration of newly added folder is available on configuration change event', async () => {

View file

@ -24,6 +24,9 @@ export class CodeEditorService extends AbstractCodeEditorService {
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
super(themeService);
this.registerCodeEditorOpenHandler(this.doOpenCodeEditor.bind(this));
this.registerCodeEditorOpenHandler(this.doOpenCodeEditorFromDiff.bind(this));
}
getActiveCodeEditor(): ICodeEditor | null {
@ -44,7 +47,7 @@ export class CodeEditorService extends AbstractCodeEditorService {
return null;
}
async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
private async doOpenCodeEditorFromDiff(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
// Special case: If the active editor is a diff editor and the request to open originates and
// targets the modified side of it, we just apply the request there to prevent opening the modified
@ -66,10 +69,10 @@ export class CodeEditorService extends AbstractCodeEditorService {
return targetEditor;
}
// Open using our normal editor service
return this.doOpenCodeEditor(input, source, sideBySide);
return null;
}
// Open using our normal editor service
private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
// Special case: we want to detect the request to open an editor that

View file

@ -47,7 +47,7 @@ export const enum GroupsArrangement {
* Make the current active group consume the maximum
* amount of space possible.
*/
MINIMIZE_OTHERS,
MAXIMIZE,
/**
* Size all groups evenly.

View file

@ -1661,7 +1661,7 @@ suite('EditorService', () => {
editor = await service.openEditor(input2, { pinned: true, activation: EditorActivation.ACTIVATE }, sideGroup);
assert.strictEqual(part.activeGroup, sideGroup);
part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
part.arrangeGroups(GroupsArrangement.MAXIMIZE);
editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.RESTORE }, rootGroup);
assert.strictEqual(part.activeGroup, sideGroup);
});
@ -1681,13 +1681,13 @@ suite('EditorService', () => {
assert.strictEqual(part.activeGroup, sideGroup);
assert.notStrictEqual(rootGroup, sideGroup);
part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS, part.activeGroup);
part.arrangeGroups(GroupsArrangement.MAXIMIZE, part.activeGroup);
await rootGroup.closeEditor(input2);
assert.strictEqual(part.activeGroup, sideGroup);
assert.strictEqual(rootGroup.isMinimized, true);
assert.strictEqual(part.activeGroup.isMinimized, false);
assert(!part.isGroupMaximized(rootGroup));
assert(part.isGroupMaximized(part.activeGroup));
});
test('active editor change / visible editor change events', async function () {

View file

@ -23,7 +23,7 @@ export class BrowserStorageService extends AbstractStorageService {
private applicationStorage: IStorage | undefined;
private applicationStorageDatabase: IIndexedDBStorageDatabase | undefined;
private readonly applicationStoragePromise = new DeferredPromise<{ indededDb: IIndexedDBStorageDatabase; storage: IStorage }>();
private readonly applicationStoragePromise = new DeferredPromise<{ indexedDb: IIndexedDBStorageDatabase; storage: IStorage }>();
private profileStorage: IStorage | undefined;
private profileStorageDatabase: IIndexedDBStorageDatabase | undefined;
@ -92,7 +92,7 @@ export class BrowserStorageService extends AbstractStorageService {
this.updateIsNew(this.applicationStorage);
this.applicationStoragePromise.complete({ indededDb: applicationStorageIndexedDB, storage: this.applicationStorage });
this.applicationStoragePromise.complete({ indexedDb: applicationStorageIndexedDB, storage: this.applicationStorage });
}
private async createProfileStorage(profile: IUserDataProfile): Promise<void> {
@ -110,22 +110,24 @@ export class BrowserStorageService extends AbstractStorageService {
// avoid creating the storage library a second time on
// the same DB.
const { indededDb: applicationStorageIndexedDB, storage: applicationStorage } = await this.applicationStoragePromise.p;
const { indexedDb: applicationStorageIndexedDB, storage: applicationStorage } = await this.applicationStoragePromise.p;
this.profileStorageDatabase = applicationStorageIndexedDB;
this.profileStorage = applicationStorage;
this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key)));
} else {
const profileStorageIndexedDB = await IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.PROFILE), broadcastChanges: true }, this.logService);
this.profileStorageDatabase = this.profileStorageDisposables.add(profileStorageIndexedDB);
this.profileStorage = this.profileStorageDisposables.add(new Storage(this.profileStorageDatabase));
this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key)));
await this.profileStorage.init();
this.updateIsNew(this.profileStorage);
}
this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key)));
await this.profileStorage.init();
this.updateIsNew(this.profileStorage);
}
private async createWorkspaceStorage(): Promise<void> {

View file

@ -853,7 +853,6 @@ export class TestEditorGroupView implements IEditorGroupView {
titleHeight!: IEditorGroupTitleHeight;
isEmpty = true;
isMinimized = false;
onWillDispose: Event<void> = Event.None;
onDidModelChange: Event<IGroupModelChangeEvent> = Event.None;

View file

@ -7,10 +7,9 @@ declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/145374
export interface TextEdit {
interface WorkspaceEdit {
// will be merged with newText
// will NOT be supported everywhere, only: `workspace.applyEdit`
newText2?: string | SnippetString;
// todo@API have a SnippetTextEdit and allow to set that?
replace(uri: Uri, range: Range, newText: string | SnippetString, metadata?: WorkspaceEditEntryMetadata): void;
}
}