Merge branch 'main' into joao/esrp-errors

This commit is contained in:
João Moreno 2021-09-20 10:45:08 +02:00
commit d390635c56
No known key found for this signature in database
GPG key ID: 896B853774D1A575
106 changed files with 1396 additions and 1006 deletions

View file

@ -9,6 +9,7 @@ const es = require("event-stream");
const vfs = require("vinyl-fs");
const util = require("../lib/util");
const merge = require("gulp-merge-json");
const gzip = require("gulp-gzip");
const azure = require('gulp-azure-storage');
const root = path.dirname(path.dirname(__dirname));
const commit = util.getVersion(root);
@ -16,6 +17,7 @@ function main() {
return es.merge(vfs.src('out-vscode-min/nls.metadata.json', { base: 'out-vscode-min' }), vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
.pipe(merge({
fileName: 'combined.nls.metadata.json',
jsonSpace: '',
edit: (parsedJson, file) => {
let key;
if (file.base === 'out-vscode-min') {
@ -64,6 +66,7 @@ function main() {
return { [key]: parsedJson };
},
}))
.pipe(gzip({ append: false }))
.pipe(vfs.dest('./nlsMetadata'))
.pipe(es.through(function (data) {
console.log(`Uploading ${data.path}`);
@ -75,7 +78,11 @@ function main() {
account: process.env.AZURE_STORAGE_ACCOUNT,
key: process.env.AZURE_STORAGE_ACCESS_KEY,
container: 'nlsmetadata',
prefix: commit + '/'
prefix: commit + '/',
contentSettings: {
contentEncoding: 'gzip',
cacheControl: 'max-age=31536000, public'
}
}));
}
main();

View file

@ -11,6 +11,7 @@ import * as Vinyl from 'vinyl';
import * as vfs from 'vinyl-fs';
import * as util from '../lib/util';
import * as merge from 'gulp-merge-json';
import * as gzip from 'gulp-gzip';
const azure = require('gulp-azure-storage');
const root = path.dirname(path.dirname(__dirname));
@ -30,6 +31,7 @@ function main() {
vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
.pipe(merge({
fileName: 'combined.nls.metadata.json',
jsonSpace: '',
edit: (parsedJson, file) => {
let key;
if (file.base === 'out-vscode-min') {
@ -82,6 +84,7 @@ function main() {
return { [key]: parsedJson };
},
}))
.pipe(gzip({ append: false }))
.pipe(vfs.dest('./nlsMetadata'))
.pipe(es.through(function (data: Vinyl) {
console.log(`Uploading ${data.path}`);
@ -93,7 +96,11 @@ function main() {
account: process.env.AZURE_STORAGE_ACCOUNT,
key: process.env.AZURE_STORAGE_ACCESS_KEY,
container: 'nlsmetadata',
prefix: commit + '/'
prefix: commit + '/',
contentSettings: {
contentEncoding: 'gzip',
cacheControl: 'max-age=31536000, public'
}
}));
}

View file

@ -48,13 +48,6 @@ export class GitFileSystemProvider implements FileSystemProvider {
model.onDidChangeRepository(this.onDidChangeRepository, this),
model.onDidChangeOriginalResource(this.onDidChangeOriginalResource, this),
workspace.registerFileSystemProvider('git', this, { isReadonly: true, isCaseSensitive: true }),
workspace.registerResourceLabelFormatter({
scheme: 'git',
formatting: {
label: '${path} (git)',
separator: '/'
}
})
);
setInterval(() => this.cleanup(), FIVE_MINUTES);

View file

@ -3,13 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window, workspace, Disposable, TextDocumentContentChangeEvent, TextDocument, Position, SnippetString } from 'vscode';
import { window, workspace, Disposable, TextDocument, Position, SnippetString, TextDocumentChangeEvent, TextDocumentChangeReason } from 'vscode';
import { Runtime } from './htmlClient';
export function activateTagClosing(tagProvider: (document: TextDocument, position: Position) => Thenable<string>, supportedLanguages: { [id: string]: boolean }, configName: string, runtime: Runtime): Disposable {
let disposables: Disposable[] = [];
workspace.onDidChangeTextDocument(event => onDidChangeTextDocument(event.document, event.contentChanges), null, disposables);
const disposables: Disposable[] = [];
workspace.onDidChangeTextDocument(onDidChangeTextDocument, null, disposables);
let isEnabled = false;
updateEnabledState();
@ -25,11 +25,11 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio
function updateEnabledState() {
isEnabled = false;
let editor = window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
let document = editor.document;
const document = editor.document;
if (!supportedLanguages[document.languageId]) {
return;
}
@ -39,33 +39,34 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio
isEnabled = true;
}
function onDidChangeTextDocument(document: TextDocument, changes: readonly TextDocumentContentChangeEvent[]) {
if (!isEnabled) {
function onDidChangeTextDocument({ document, contentChanges, reason }: TextDocumentChangeEvent) {
if (!isEnabled || contentChanges.length === 0 || reason === TextDocumentChangeReason.Undo) {
return;
}
let activeDocument = window.activeTextEditor && window.activeTextEditor.document;
if (document !== activeDocument || changes.length === 0) {
const activeDocument = window.activeTextEditor && window.activeTextEditor.document;
if (document !== activeDocument) {
return;
}
if (timeout) {
timeout.dispose();
}
let lastChange = changes[changes.length - 1];
let lastCharacter = lastChange.text[lastChange.text.length - 1];
const lastChange = contentChanges[contentChanges.length - 1];
const lastCharacter = lastChange.text[lastChange.text.length - 1];
if (lastChange.rangeLength > 0 || lastCharacter !== '>' && lastCharacter !== '/') {
return;
}
let rangeStart = lastChange.range.start;
let version = document.version;
const rangeStart = lastChange.range.start;
const version = document.version;
timeout = runtime.timer.setTimeout(() => {
let position = new Position(rangeStart.line, rangeStart.character + lastChange.text.length);
const position = new Position(rangeStart.line, rangeStart.character + lastChange.text.length);
tagProvider(document, position).then(text => {
if (text && isEnabled) {
let activeEditor = window.activeTextEditor;
const activeEditor = window.activeTextEditor;
if (activeEditor) {
let activeDocument = activeEditor.document;
const activeDocument = activeEditor.document;
if (document === activeDocument && activeDocument.version === version) {
let selections = activeEditor.selections;
const selections = activeEditor.selections;
if (selections.length && selections.some(s => s.active.isEqual(position))) {
activeEditor.insertSnippet(new SnippetString(text), selections.map(s => s.active));
} else {

View file

@ -22,7 +22,7 @@ export class CapabilitiesStatus extends Disposable {
this._statusItem = this._register(vscode.languages.createLanguageStatusItem('typescript.capabilities', jsTsLanguageModes));
this._statusItem.name = localize('capabilitiesStatus.name', "IntelliSense IntelliSense Status");
this._statusItem.name = localize('capabilitiesStatus.name', "IntelliSense Status");
this._register(this._client.onTsServerStarted(() => this.update()));
this._register(this._client.onDidChangeCapabilities(() => this.update()));

View file

@ -230,7 +230,7 @@ import { assertNoRpc } from '../utils';
test('onDidChangeTerminalState should fire after writing to a terminal', async () => {
const terminal = window.createTerminal();
deepStrictEqual(terminal.state, { interactedWith: false });
deepStrictEqual(terminal.state, { isInteractedWith: false });
const eventState = await new Promise<TerminalState>(r => {
disposables.push(window.onDidChangeTerminalState(e => {
if (e === terminal) {
@ -239,8 +239,8 @@ import { assertNoRpc } from '../utils';
}));
terminal.sendText('test');
});
deepStrictEqual(eventState, { interactedWith: true });
deepStrictEqual(terminal.state, { interactedWith: true });
deepStrictEqual(eventState, { isInteractedWith: true });
deepStrictEqual(terminal.state, { isInteractedWith: true });
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(t => {
if (t === terminal) {

View file

@ -349,24 +349,17 @@ suite('vscode API - window', () => {
//#region Tabs API tests
test('Tabs - Ensure tabs getter is correct', async () => {
const docA = await workspace.openTextDocument(await createRandomFile());
const docB = await workspace.openTextDocument(await createRandomFile());
const docC = await workspace.openTextDocument(await createRandomFile());
// Add back actual notebook doc once stuck promise is figured out
//const notebookDoc = await workspace.openNotebookDocument(await createRandomFile('', undefined, '.vsctestnb'));
const notebookDoc = await workspace.openTextDocument(await createRandomFile());
// const [docA, docB, docC, notebookDoc] = await Promise.all([
// workspace.openTextDocument(await createRandomFile()),
// workspace.openTextDocument(await createRandomFile()),
// workspace.openTextDocument(await createRandomFile()),
// workspace.openNotebookDocument(await createRandomFile('', undefined, '.vsctestnb'))
// ]);
const [docA, docB, docC, notebookDoc] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openNotebookDocument('jupyter-notebook', undefined)
]);
await window.showTextDocument(docA, { viewColumn: ViewColumn.One, preview: false });
await window.showTextDocument(docB, { viewColumn: ViewColumn.Two, preview: false });
await window.showTextDocument(docC, { viewColumn: ViewColumn.Three, preview: false });
await window.showTextDocument(notebookDoc, { viewColumn: ViewColumn.One, preview: false });
//await window.showNotebookDocument(notebookDoc, { viewColumn: ViewColumn.One, preview: false });
await window.showNotebookDocument(notebookDoc, { viewColumn: ViewColumn.One, preview: false });
const leftDiff = await createRandomFile();
const rightDiff = await createRandomFile();

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.61.0",
"distro": "7f4d861ce33b303be575006848803ad7a523c029",
"distro": "c464d76c12ba556d77206699e13ce63655e14222",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -1367,7 +1367,7 @@ export function detectFullscreen(): IDetectedFullscreen | null {
export function safeInnerHtml(node: HTMLElement, value: string): void {
const options: dompurify.Config = {
ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
ALLOWED_ATTR: ['href', 'data-href', 'data-command', 'target', 'title', 'name', 'src', 'alt', 'class', 'id', 'role', 'tabindex', 'style', 'data-code', 'width', 'height', 'align', 'x-dispatch', 'required', 'checked', 'placeholder'],
ALLOWED_ATTR: ['href', 'data-href', 'data-command', 'target', 'title', 'name', 'src', 'alt', 'class', 'id', 'role', 'tabindex', 'style', 'data-code', 'width', 'height', 'align', 'x-dispatch', 'required', 'checked', 'placeholder', 'type'],
RETURN_DOM: false,
RETURN_DOM_FRAGMENT: false,
};

View file

@ -31,7 +31,7 @@ export class BaseDropdown extends ActionRunner {
private contents?: HTMLElement;
private visible: boolean | undefined;
private _onDidChangeVisibility = new Emitter<boolean>();
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
constructor(container: HTMLElement, options: IBaseDropdownOptions) {

View file

@ -31,6 +31,7 @@ export interface IFindInputOptions extends IFindInputStyles {
readonly appendWholeWordsLabel?: string;
readonly appendRegexLabel?: string;
readonly history?: string[];
readonly showHistoryHint?: () => boolean;
}
export interface IFindInputStyles extends IInputBoxStyles {
@ -150,6 +151,7 @@ export class FindInput extends Widget {
inputValidationErrorForeground: this.inputValidationErrorForeground,
inputValidationErrorBorder: this.inputValidationErrorBorder,
history,
showHistoryHint: options.showHistoryHint,
flexibleHeight,
flexibleWidth,
flexibleMaxHeight

View file

@ -30,6 +30,7 @@ export interface IReplaceInputOptions extends IReplaceInputStyles {
readonly appendPreserveCaseLabel?: string;
readonly history?: string[];
readonly showHistoryHint?: () => boolean;
}
export interface IReplaceInputStyles extends IInputBoxStyles {
@ -157,6 +158,7 @@ export class ReplaceInput extends Widget {
inputValidationErrorForeground: this.inputValidationErrorForeground,
inputValidationErrorBorder: this.inputValidationErrorBorder,
history,
showHistoryHint: options.showHistoryHint,
flexibleHeight,
flexibleWidth,
flexibleMaxHeight

View file

@ -9,7 +9,7 @@ import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget, IHoverWidg
import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { isFunction, isString } from 'vs/base/common/types';
import { localize } from 'vs/nls';
@ -93,9 +93,9 @@ class UpdatableHoverWidget implements IDisposable {
private show(content: ResolvedMarkdownTooltipContent): void {
const oldHoverWidget = this._hoverWidget;
if (content) {
if (this.hasContent(content)) {
const hoverOptions: IHoverDelegateOptions = {
content: content,
content,
target: this.target,
showPointer: this.hoverDelegate.placement === 'element',
hoverPosition: HoverPosition.BELOW,
@ -107,6 +107,18 @@ class UpdatableHoverWidget implements IDisposable {
oldHoverWidget?.dispose();
}
private hasContent(content: ResolvedMarkdownTooltipContent): content is NonNullable<ResolvedMarkdownTooltipContent> {
if (!content) {
return false;
}
if (isMarkdownString(content)) {
return this.hasContent(content.value);
}
return true;
}
get isDisposed() {
return this._hoverWidget?.isDisposed;
}

View file

@ -87,7 +87,7 @@
opacity: 0.75;
font-size: 90%;
font-weight: 600;
margin: 0 16px 0 5px;
margin: auto 16px 0 5px; /* https://github.com/microsoft/vscode/issues/113223 */
text-align: center;
}

View file

@ -92,11 +92,11 @@ const defaultOpts = {
export class InputBox extends Widget {
private contextViewProvider?: IContextViewProvider;
element: HTMLElement;
private input: HTMLInputElement;
protected input: HTMLInputElement;
private actionbar?: ActionBar;
private options: IInputOptions;
private message: IMessage | null;
private placeholder: string;
protected placeholder: string;
private tooltip: string;
private ariaLabel: string;
private validation?: IInputValidator;
@ -634,15 +634,75 @@ export class InputBox extends Widget {
export interface IHistoryInputOptions extends IInputOptions {
history: string[];
readonly showHistoryHint?: () => boolean;
}
export class HistoryInputBox extends InputBox implements IHistoryNavigationWidget {
private readonly history: HistoryNavigator<string>;
private observer: MutationObserver | undefined;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions) {
const NLS_PLACEHOLDER_HISTORY_HINT = nls.localize({ key: 'history.inputbox.hint', comment: ['Text will be prefixed with \u21C5 plus a single space, then used as a hint where input field keeps history'] }, "for history");
const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX = ` or \u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT}`;
const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS = ` (\u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT})`;
super(container, contextViewProvider, options);
this.history = new HistoryNavigator<string>(options.history, 100);
// Function to append the history suffix to the placeholder if necessary
const addSuffix = () => {
if (options.showHistoryHint && options.showHistoryHint() && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX) && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS) && this.history.getHistory().length) {
const suffix = this.placeholder.endsWith(')') ? NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX : NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS;
const suffixedPlaceholder = this.placeholder + suffix;
if (options.showPlaceholderOnFocus && document.activeElement !== this.input) {
this.placeholder = suffixedPlaceholder;
}
else {
this.setPlaceHolder(suffixedPlaceholder);
}
}
};
// Spot the change to the textarea class attribute which occurs when it changes between non-empty and empty,
// and add the history suffix to the placeholder if not yet present
this.observer = new MutationObserver((mutationList: MutationRecord[], observer: MutationObserver) => {
mutationList.forEach((mutation: MutationRecord) => {
if (!mutation.target.textContent) {
addSuffix();
}
});
});
this.observer.observe(this.input, { attributeFilter: ['class'] });
this.onfocus(this.input, () => addSuffix());
this.onblur(this.input, () => {
const resetPlaceholder = (historyHint: string) => {
if (!this.placeholder.endsWith(historyHint)) {
return false;
}
else {
const revertedPlaceholder = this.placeholder.slice(0, this.placeholder.length - historyHint.length);
if (options.showPlaceholderOnFocus) {
this.placeholder = revertedPlaceholder;
}
else {
this.setPlaceHolder(revertedPlaceholder);
}
return true;
}
};
if (!resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS)) {
resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX);
}
});
}
override dispose() {
super.dispose();
if (this.observer) {
this.observer.disconnect();
this.observer = undefined;
}
}
public addToHistory(): void {

View file

@ -407,6 +407,7 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o
}
// Flush contents (not metadata) of the file to disk
// https://github.com/microsoft/vscode/issues/9589
fs.fdatasync(fd, (syncError: Error | null) => {
// In some exotic setups it is well possible that node fails to sync
@ -444,7 +445,7 @@ export function writeFileSync(path: string, data: string | Buffer, options?: IWr
// Flush contents (not metadata) of the file to disk
try {
fs.fdatasyncSync(fd);
fs.fdatasyncSync(fd); // https://github.com/microsoft/vscode/issues/9589
} catch (syncError) {
console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError);
canFlush = false;

View file

@ -279,8 +279,7 @@ class SharedProcessMain extends Disposable {
new PtyHostService({
graceTime: LocalReconnectConstants.GraceTime,
shortGraceTime: LocalReconnectConstants.ShortGraceTime,
scrollback: configurationService.getValue<number>(TerminalSettingId.PersistentSessionScrollback) ?? 100,
useExperimentalSerialization: configurationService.getValue<boolean>(TerminalSettingId.PersistentSessionExperimentalSerializer) ?? true,
scrollback: configurationService.getValue<number>(TerminalSettingId.PersistentSessionScrollback) ?? 100
},
configurationService,
logService,

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { tail } from 'vs/base/common/arrays';
import { DenseKeyProvider, SmallImmutableSet } from './smallImmutableSet';
import { SmallImmutableSet } from './smallImmutableSet';
import { lengthAdd, lengthZero, Length, lengthHash } from './length';
export const enum AstNodeKind {
@ -20,7 +20,7 @@ export type AstNode = PairAstNode | ListAstNode | BracketAstNode | InvalidBracke
abstract class BaseAstNode {
abstract readonly kind: AstNodeKind;
abstract readonly children: readonly AstNode[];
abstract readonly unopenedBrackets: SmallImmutableSet<number>;
abstract readonly missingBracketIds: SmallImmutableSet<number>;
/**
* In case of a list, determines the height of the (2,3) tree.
@ -55,7 +55,6 @@ abstract class BaseAstNode {
export class PairAstNode extends BaseAstNode {
public static create(
category: number,
openingBracket: BracketAstNode,
child: AstNode | null,
closingBracket: BracketAstNode | null
@ -71,7 +70,7 @@ export class PairAstNode extends BaseAstNode {
children.push(closingBracket);
}
return new PairAstNode(length, category, children, child ? child.unopenedBrackets : SmallImmutableSet.getEmpty());
return new PairAstNode(length, children, child ? child.missingBracketIds : SmallImmutableSet.getEmpty());
}
get kind(): AstNodeKind.Pair {
@ -82,7 +81,7 @@ export class PairAstNode extends BaseAstNode {
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
openedBracketIds: SmallImmutableSet<number>,
endLineDidChange: boolean
) {
if (this.closingBracket === null) {
@ -96,7 +95,7 @@ export class PairAstNode extends BaseAstNode {
return false;
}
if (expectedClosingCategories.intersects(this.unopenedBrackets)) {
if (openedBracketIds.intersects(this.missingBracketIds)) {
return false;
}
@ -105,7 +104,6 @@ export class PairAstNode extends BaseAstNode {
flattenLists(): PairAstNode {
return PairAstNode.create(
this.category,
this.openingBracket.flattenLists(),
this.child && this.child.flattenLists(),
this.closingBracket && this.closingBracket.flattenLists()
@ -138,9 +136,8 @@ export class PairAstNode extends BaseAstNode {
private constructor(
length: Length,
public readonly category: number,
public readonly children: readonly AstNode[],
public readonly unopenedBrackets: SmallImmutableSet<number>
public readonly missingBracketIds: SmallImmutableSet<number>
) {
super(length);
}
@ -148,9 +145,8 @@ export class PairAstNode extends BaseAstNode {
clone(): PairAstNode {
return new PairAstNode(
this.length,
this.category,
clone(this.children),
this.unopenedBrackets
this.missingBracketIds
);
}
}
@ -172,10 +168,10 @@ export class ListAstNode extends BaseAstNode {
return new ListAstNode(lengthZero, 0, items, SmallImmutableSet.getEmpty());
} else {
let length = items[0].length;
let unopenedBrackets = items[0].unopenedBrackets;
let unopenedBrackets = items[0].missingBracketIds;
for (let i = 1; i < items.length; i++) {
length = lengthAdd(length, items[i].length);
unopenedBrackets = unopenedBrackets.merge(items[i].unopenedBrackets);
unopenedBrackets = unopenedBrackets.merge(items[i].missingBracketIds);
}
return new ListAstNode(length, items[0].listHeight + 1, items, unopenedBrackets);
}
@ -187,7 +183,7 @@ export class ListAstNode extends BaseAstNode {
get children(): readonly AstNode[] {
return this._items;
}
get unopenedBrackets(): SmallImmutableSet<number> {
get missingBracketIds(): SmallImmutableSet<number> {
return this._unopenedBrackets;
}
@ -201,7 +197,7 @@ export class ListAstNode extends BaseAstNode {
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
openedBracketIds: SmallImmutableSet<number>,
endLineDidChange: boolean
): boolean {
if (this._items.length === 0) {
@ -209,7 +205,7 @@ export class ListAstNode extends BaseAstNode {
return true;
}
if (expectedClosingCategories.intersects(this.unopenedBrackets)) {
if (openedBracketIds.intersects(this.missingBracketIds)) {
return false;
}
@ -219,7 +215,7 @@ export class ListAstNode extends BaseAstNode {
}
return lastChild.canBeReused(
expectedClosingCategories,
openedBracketIds,
endLineDidChange
);
}
@ -238,7 +234,7 @@ export class ListAstNode extends BaseAstNode {
}
clone(): ListAstNode {
return new ListAstNode(this.length, this.listHeight, clone(this._items), this.unopenedBrackets);
return new ListAstNode(this.length, this.listHeight, clone(this._items), this.missingBracketIds);
}
private handleChildrenChanged(): void {
@ -248,10 +244,10 @@ export class ListAstNode extends BaseAstNode {
}
let length = items[0].length;
let unopenedBrackets = items[0].unopenedBrackets;
let unopenedBrackets = items[0].missingBracketIds;
for (let i = 1; i < items.length; i++) {
length = lengthAdd(length, items[i].length);
unopenedBrackets = unopenedBrackets.merge(items[i].unopenedBrackets);
unopenedBrackets = unopenedBrackets.merge(items[i].missingBracketIds);
}
this._length = length;
this._unopenedBrackets = unopenedBrackets;
@ -372,12 +368,12 @@ export class TextAstNode extends BaseAstNode {
get children(): readonly AstNode[] {
return emptyArray;
}
get unopenedBrackets(): SmallImmutableSet<number> {
get missingBracketIds(): SmallImmutableSet<number> {
return SmallImmutableSet.getEmpty();
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
openedBracketIds: SmallImmutableSet<number>,
endLineDidChange: boolean
) {
// Don't reuse text from a line that got changed.
@ -422,7 +418,7 @@ export class BracketAstNode extends BaseAstNode {
return emptyArray;
}
get unopenedBrackets(): SmallImmutableSet<number> {
get missingBracketIds(): SmallImmutableSet<number> {
return SmallImmutableSet.getEmpty();
}
@ -456,18 +452,18 @@ export class InvalidBracketAstNode extends BaseAstNode {
return emptyArray;
}
public readonly unopenedBrackets: SmallImmutableSet<number>;
public readonly missingBracketIds: SmallImmutableSet<number>;
constructor(category: number, length: Length, denseKeyProvider: DenseKeyProvider<number>) {
constructor(closingBrackets: SmallImmutableSet<number>, length: Length) {
super(length);
this.unopenedBrackets = SmallImmutableSet.getEmpty().add(category, denseKeyProvider);
this.missingBracketIds = closingBrackets;
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
openedBracketIds: SmallImmutableSet<number>,
endLineDidChange: boolean
) {
return !expectedClosingCategories.intersects(this.unopenedBrackets);
return !openedBracketIds.intersects(this.missingBracketIds);
}
flattenLists(): InvalidBracketAstNode {

View file

@ -120,8 +120,8 @@ class BracketPairColorizerImpl extends Disposable implements DecorationProvider
private initialAstWithoutTokens: AstNode | undefined;
private astWithTokens: AstNode | undefined;
private readonly brackets = new LanguageAgnosticBracketTokens([]);
private readonly denseKeyProvider = new DenseKeyProvider<number>();
private readonly denseKeyProvider = new DenseKeyProvider<string>();
private readonly brackets = new LanguageAgnosticBracketTokens(this.denseKeyProvider);
public didLanguageChange(languageId: LanguageId): boolean {
return this.brackets.didLanguageChange(languageId);
@ -159,7 +159,7 @@ class BracketPairColorizerImpl extends Disposable implements DecorationProvider
// There are no token information yet
const brackets = this.brackets.getSingleLanguageBracketTokens(this.textModel.getLanguageIdentifier().id);
const tokenizer = new FastTokenizer(this.textModel.getValue(), brackets);
this.initialAstWithoutTokens = parseDocument(tokenizer, [], undefined, this.denseKeyProvider);
this.initialAstWithoutTokens = parseDocument(tokenizer, [], undefined);
this.astWithTokens = this.initialAstWithoutTokens.clone();
} else if (textModel.backgroundTokenizationState === BackgroundTokenizationState.Completed) {
// Skip the initial ast, as there is no flickering.
@ -196,7 +196,7 @@ class BracketPairColorizerImpl extends Disposable implements DecorationProvider
const isPure = false;
const previousAstClone = isPure ? previousAst?.clone() : previousAst;
const tokenizer = new TextBufferTokenizer(this.textModel, this.brackets);
const result = parseDocument(tokenizer, edits, previousAstClone, this.denseKeyProvider);
const result = parseDocument(tokenizer, edits, previousAstClone);
return result;
}

View file

@ -4,51 +4,70 @@
*--------------------------------------------------------------------------------------------*/
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { toLength } from 'vs/editor/common/model/bracketPairColorizer/length';
import { SmallImmutableSet, DenseKeyProvider, identityKeyProvider } from 'vs/editor/common/model/bracketPairColorizer/smallImmutableSet';
import { LanguageId } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { BracketAstNode } from './ast';
import { toLength } from './length';
import { Token, TokenKind } from './tokenizer';
import { OpeningBracketId, Token, TokenKind } from './tokenizer';
export class BracketTokens {
static createFromLanguage(languageId: LanguageId, customBracketPairs: readonly [string, string][]): BracketTokens {
static createFromLanguage(languageId: LanguageId, denseKeyProvider: DenseKeyProvider<string>): BracketTokens {
function getId(languageId: LanguageId, openingText: string): OpeningBracketId {
return denseKeyProvider.getKey(`${languageId}:::${openingText}`);
}
const brackets = [...(LanguageConfigurationRegistry.getColorizedBracketPairs(languageId))];
const tokens = new BracketTokens();
const closingBrackets = new Map</* closingText */ string, { openingBrackets: SmallImmutableSet<OpeningBracketId>, first: OpeningBracketId }>();
const openingBrackets = new Set</* openingText */ string>();
let idxOffset = 0;
for (const pair of brackets) {
tokens.addBracket(languageId, pair[0], TokenKind.OpeningBracket, idxOffset);
tokens.addBracket(languageId, pair[1], TokenKind.ClosingBracket, idxOffset);
idxOffset++;
for (const [openingText, closingText] of brackets) {
openingBrackets.add(openingText);
let info = closingBrackets.get(closingText);
const openingTextId = getId(languageId, openingText);
if (!info) {
info = { openingBrackets: SmallImmutableSet.getEmpty(), first: openingTextId };
closingBrackets.set(closingText, info);
}
info.openingBrackets = info.openingBrackets.add(openingTextId, identityKeyProvider);
}
for (const pair of customBracketPairs) {
idxOffset++;
tokens.addBracket(languageId, pair[0], TokenKind.OpeningBracket, idxOffset);
tokens.addBracket(languageId, pair[1], TokenKind.ClosingBracket, idxOffset);
const map = new Map<string, Token>();
for (const [closingText, info] of closingBrackets) {
const length = toLength(0, closingText.length);
map.set(closingText, new Token(
length,
TokenKind.ClosingBracket,
info.first,
info.openingBrackets,
BracketAstNode.create(length)
));
}
return tokens;
for (const openingText of openingBrackets) {
const length = toLength(0, openingText.length);
const openingTextId = getId(languageId, openingText);
map.set(openingText, new Token(
length,
TokenKind.OpeningBracket,
openingTextId,
SmallImmutableSet.getEmpty().add(openingTextId, identityKeyProvider),
BracketAstNode.create(length)
));
}
return new BracketTokens(map);
}
private hasRegExp = false;
private _regExpGlobal: RegExp | null = null;
private readonly map = new Map<string, Token>();
private addBracket(languageId: LanguageId, value: string, kind: TokenKind, idx: number): void {
const length = toLength(0, value.length);
this.map.set(value,
new Token(
length,
kind,
// A language can have at most 1000 bracket pairs.
languageId * 1000 + idx,
languageId,
BracketAstNode.create(length)
)
);
}
constructor(
private readonly map: Map<string, Token>
) { }
getRegExpStr(): string | null {
if (this.isEmpty) {
@ -85,7 +104,7 @@ export class BracketTokens {
export class LanguageAgnosticBracketTokens {
private readonly languageIdToBracketTokens: Map<LanguageId, BracketTokens> = new Map();
constructor(private readonly customBracketPairs: readonly [string, string][]) {
constructor(private readonly denseKeyProvider: DenseKeyProvider<string>) {
}
public didLanguageChange(languageId: LanguageId): boolean {
@ -93,14 +112,14 @@ export class LanguageAgnosticBracketTokens {
if (!existing) {
return false;
}
const newRegExpStr = BracketTokens.createFromLanguage(languageId, this.customBracketPairs).getRegExpStr();
const newRegExpStr = BracketTokens.createFromLanguage(languageId, this.denseKeyProvider).getRegExpStr();
return existing.getRegExpStr() !== newRegExpStr;
}
getSingleLanguageBracketTokens(languageId: LanguageId): BracketTokens {
let singleLanguageBracketTokens = this.languageIdToBracketTokens.get(languageId);
if (!singleLanguageBracketTokens) {
singleLanguageBracketTokens = BracketTokens.createFromLanguage(languageId, this.customBracketPairs);
singleLanguageBracketTokens = BracketTokens.createFromLanguage(languageId, this.denseKeyProvider);
this.languageIdToBracketTokens.set(languageId, singleLanguageBracketTokens);
}
return singleLanguageBracketTokens;

View file

@ -5,14 +5,14 @@
import { AstNode, AstNodeKind, BracketAstNode, InvalidBracketAstNode, ListAstNode, PairAstNode, TextAstNode } from './ast';
import { BeforeEditPositionMapper, TextEditInfo } from './beforeEditPositionMapper';
import { DenseKeyProvider, SmallImmutableSet } from './smallImmutableSet';
import { SmallImmutableSet } from './smallImmutableSet';
import { lengthGetLineCount, lengthIsZero, lengthLessThanEqual } from './length';
import { concat23Trees } from './concat23Trees';
import { NodeReader } from './nodeReader';
import { Tokenizer, TokenKind } from './tokenizer';
import { OpeningBracketId, Tokenizer, TokenKind } from './tokenizer';
export function parseDocument(tokenizer: Tokenizer, edits: TextEditInfo[], oldNode: AstNode | undefined, denseKeyProvider: DenseKeyProvider<number>): AstNode {
const parser = new Parser(tokenizer, edits, oldNode, denseKeyProvider);
export function parseDocument(tokenizer: Tokenizer, edits: TextEditInfo[], oldNode: AstNode | undefined): AstNode {
const parser = new Parser(tokenizer, edits, oldNode);
return parser.parseDocument();
}
@ -40,7 +40,6 @@ class Parser {
private readonly tokenizer: Tokenizer,
edits: TextEditInfo[],
oldNode: AstNode | undefined,
private readonly denseKeyProvider: DenseKeyProvider<number>,
) {
this.oldNodeReader = oldNode ? new NodeReader(oldNode) : undefined;
this.positionMapper = new BeforeEditPositionMapper(edits, tokenizer.length);
@ -59,7 +58,7 @@ class Parser {
}
private parseList(
expectedClosingCategories: SmallImmutableSet<number>,
openedBracketIds: SmallImmutableSet<OpeningBracketId>,
): AstNode | null {
const items = new Array<AstNode>();
@ -68,12 +67,12 @@ class Parser {
if (
!token ||
(token.kind === TokenKind.ClosingBracket &&
expectedClosingCategories.has(token.category, this.denseKeyProvider))
token.bracketIds.intersects(openedBracketIds))
) {
break;
}
const child = this.parseChild(expectedClosingCategories);
const child = this.parseChild(openedBracketIds);
if (child.kind === AstNodeKind.List && child.children.length === 0) {
continue;
}
@ -86,7 +85,7 @@ class Parser {
}
private parseChild(
expectingClosingCategories: SmallImmutableSet<number>,
openedBracketIds: SmallImmutableSet<number>,
): AstNode {
if (this.oldNodeReader) {
const maxCacheableLength = this.positionMapper.getDistanceToNextChange(this.tokenizer.offset);
@ -97,7 +96,7 @@ class Parser {
}
const endLineDidChange = lengthGetLineCount(curNode.length) === lengthGetLineCount(maxCacheableLength);
const canBeReused = curNode.canBeReused(expectingClosingCategories, endLineDidChange);
const canBeReused = curNode.canBeReused(openedBracketIds, endLineDidChange);
return canBeReused;
});
@ -115,31 +114,29 @@ class Parser {
switch (token.kind) {
case TokenKind.ClosingBracket:
return new InvalidBracketAstNode(token.category, token.length, this.denseKeyProvider);
return new InvalidBracketAstNode(token.bracketIds, token.length);
case TokenKind.Text:
return token.astNode as TextAstNode;
case TokenKind.OpeningBracket:
const set = expectingClosingCategories.add(token.category, this.denseKeyProvider);
const set = openedBracketIds.merge(token.bracketIds);
const child = this.parseList(set);
const nextToken = this.tokenizer.peek();
if (
nextToken &&
nextToken.kind === TokenKind.ClosingBracket &&
nextToken.category === token.category
(nextToken.bracketId === token.bracketId || nextToken.bracketIds.intersects(token.bracketIds))
) {
this.tokenizer.read();
return PairAstNode.create(
token.category,
token.astNode as BracketAstNode,
child,
nextToken.astNode as BracketAstNode
);
} else {
return PairAstNode.create(
token.category,
token.astNode as BracketAstNode,
child,
null

View file

@ -37,7 +37,7 @@ export class SmallImmutableSet<T> {
) {
}
public add(value: T, keyProvider: DenseKeyProvider<T>): SmallImmutableSet<T> {
public add(value: T, keyProvider: IDenseKeyProvider<T>): SmallImmutableSet<T> {
const key = keyProvider.getKey(value);
let idx = key >> 5; // divided by 32
if (idx === 0) {
@ -59,7 +59,7 @@ export class SmallImmutableSet<T> {
return SmallImmutableSet.create(this.items, newItems);
}
public has(value: T, keyProvider: DenseKeyProvider<T>): boolean {
public has(value: T, keyProvider: IDenseKeyProvider<T>): boolean {
const key = keyProvider.getKey(value);
let idx = key >> 5; // divided by 32
if (idx === 0) {
@ -129,6 +129,16 @@ export class SmallImmutableSet<T> {
}
}
export interface IDenseKeyProvider<T> {
getKey(value: T): number;
}
export const identityKeyProvider: IDenseKeyProvider<number> = {
getKey(value: number) {
return value;
}
};
/**
* Assigns values a unique incrementing key.
*/

View file

@ -6,7 +6,8 @@
import { NotSupportedError } from 'vs/base/common/errors';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { ITextModel } from 'vs/editor/common/model';
import { LanguageId, StandardTokenType, TokenMetadata } from 'vs/editor/common/modes';
import { SmallImmutableSet } from 'vs/editor/common/model/bracketPairColorizer/smallImmutableSet';
import { StandardTokenType, TokenMetadata } from 'vs/editor/common/modes';
import { BracketAstNode, TextAstNode } from './ast';
import { BracketTokens, LanguageAgnosticBracketTokens } from './brackets';
import { lengthGetColumnCountIfZeroLineCount, Length, lengthAdd, lengthDiff, lengthToObj, lengthZero, toLength } from './length';
@ -28,12 +29,24 @@ export const enum TokenKind {
ClosingBracket = 2,
}
export type OpeningBracketId = number;
export class Token {
constructor(
readonly length: Length,
readonly kind: TokenKind,
readonly category: number,
readonly languageId: LanguageId,
/**
* If this token is an opening bracket, this is the id of the opening bracket.
* If this token is a closing bracket, this is the id of the first opening bracket that is closed by this bracket.
* Otherwise, it is -1.
*/
readonly bracketId: OpeningBracketId,
/**
* If this token is an opening bracket, this just contains `bracketId`.
* If this token is a closing bracket, this lists all opening bracket ids, that it closes.
* Otherwise, it is empty.
*/
readonly bracketIds: SmallImmutableSet<OpeningBracketId>,
readonly astNode: BracketAstNode | TextAstNode | undefined,
) { }
}
@ -229,7 +242,7 @@ class NonPeekableTextBufferTokenizer {
}
const length = lengthDiff(startLineIdx, startLineCharOffset, this.lineIdx, this.lineCharOffset);
return new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length));
return new Token(length, TokenKind.Text, -1, SmallImmutableSet.getEmpty(), new TextAstNode(length));
}
}
@ -255,7 +268,7 @@ export class FastTokenizer implements Tokenizer {
for (let i = 0; i < 60; i++) {
smallTextTokens0Line.push(
new Token(
toLength(0, i), TokenKind.Text, -1, -1,
toLength(0, i), TokenKind.Text, -1, SmallImmutableSet.getEmpty(),
new TextAstNode(toLength(0, i))
)
);
@ -265,7 +278,7 @@ export class FastTokenizer implements Tokenizer {
for (let i = 0; i < 60; i++) {
smallTextTokens1Line.push(
new Token(
toLength(1, i), TokenKind.Text, -1, -1,
toLength(1, i), TokenKind.Text, -1, SmallImmutableSet.getEmpty(),
new TextAstNode(toLength(1, i))
)
);
@ -288,7 +301,7 @@ export class FastTokenizer implements Tokenizer {
token = smallTextTokens0Line[colCount];
} else {
const length = toLength(0, colCount);
token = new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length));
token = new Token(length, TokenKind.Text, -1, SmallImmutableSet.getEmpty(), new TextAstNode(length));
}
} else {
const lineCount = curLineCount - lastTokenEndLine;
@ -297,7 +310,7 @@ export class FastTokenizer implements Tokenizer {
token = smallTextTokens1Line[colCount];
} else {
const length = toLength(lineCount, colCount);
token = new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length));
token = new Token(length, TokenKind.Text, -1, SmallImmutableSet.getEmpty(), new TextAstNode(length));
}
}
tokens.push(token);
@ -318,7 +331,7 @@ export class FastTokenizer implements Tokenizer {
const length = (lastTokenEndLine === curLineCount)
? toLength(0, offset - lastTokenEndOffset)
: toLength(curLineCount - lastTokenEndLine, offset - lastLineBreakOffset);
tokens.push(new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length)));
tokens.push(new Token(length, TokenKind.Text, -1, SmallImmutableSet.getEmpty(), new TextAstNode(length)));
}
this.length = toLength(curLineCount, offset - lastLineBreakOffset);

View file

@ -28,7 +28,13 @@ export class CharacterPairSupport {
if (config.colorizedBracketPairs) {
this._colorizedBracketPairs = config.colorizedBracketPairs.map(b => [b[0], b[1]]);
} else if (config.brackets) {
this._colorizedBracketPairs = config.brackets.map(b => [b[0], b[1]]);
this._colorizedBracketPairs = config.brackets
.map((b) => [b[0], b[1]] as [string, string])
// Many languages set < ... > as bracket pair, even though they also use it as comparison operator.
// This leads to problems when colorizing this bracket, so we exclude it by default.
// Languages can still override this by configuring `colorizedBracketPairs`
// https://github.com/microsoft/vscode/issues/132476
.filter((p) => !(p[0] === '<' && p[1] === '>'));
} else {
this._colorizedBracketPairs = [];
}

View file

@ -31,6 +31,7 @@ import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contri
import * as nls from 'vs/nls';
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget';
import { showHistoryKeybindingHint } from 'vs/platform/browser/historyWidgetKeybindingHint';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
@ -966,7 +967,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
},
flexibleHeight,
flexibleWidth,
flexibleMaxHeight: 118
flexibleMaxHeight: 118,
showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService)
}, this._contextKeyService, true));
this._findInput.setRegex(!!this._state.isRegex);
this._findInput.setCaseSensitive(!!this._state.matchCase);
@ -1105,7 +1107,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
history: [],
flexibleHeight,
flexibleWidth,
flexibleMaxHeight: 118
flexibleMaxHeight: 118,
showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService)
}, this._contextKeyService, true));
this._replaceInput.setPreserveCase(!!this._state.preserveCase);
this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e)));

View file

@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
import { DisposableStore } from 'vs/base/common/lifecycle';
import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairColorizer/brackets';
import { SmallImmutableSet, DenseKeyProvider } from 'vs/editor/common/model/bracketPairColorizer/smallImmutableSet';
import { Token, TokenKind } from 'vs/editor/common/model/bracketPairColorizer/tokenizer';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
suite('Bracket Pair Colorizer - Brackets', () => {
test('Basic', () => {
const languageId = 3;
const mode1 = new LanguageIdentifier('testMode1', languageId);
const denseKeyProvider = new DenseKeyProvider<string>();
const getImmutableSet = (elements: string[]) => {
let newSet = SmallImmutableSet.getEmpty();
elements.forEach(x => newSet = newSet.add(`${languageId}:::${x}`, denseKeyProvider));
return newSet;
};
const getKey = (value: string) => {
return denseKeyProvider.getKey(`${languageId}:::${value}`);
};
const disposableStore = new DisposableStore();
disposableStore.add(LanguageConfigurationRegistry.register(mode1, {
brackets: [
['{', '}'], ['[', ']'], ['(', ')'],
['begin', 'end'], ['case', 'endcase'], ['casez', 'endcase'], // Verilog
['\\left(', '\\right)'], ['\\left(', '\\right.'], ['\\left.', '\\right)'], // LaTeX Parentheses
['\\left[', '\\right]'], ['\\left[', '\\right.'], ['\\left.', '\\right]'] // LaTeX Brackets
]
}));
const brackets = new LanguageAgnosticBracketTokens(denseKeyProvider);
const bracketsExpected = [
{ text: '{', length: 1, kind: 'OpeningBracket', bracketId: getKey('{'), bracketIds: getImmutableSet(['{']) },
{ text: '[', length: 1, kind: 'OpeningBracket', bracketId: getKey('['), bracketIds: getImmutableSet(['[']) },
{ text: '(', length: 1, kind: 'OpeningBracket', bracketId: getKey('('), bracketIds: getImmutableSet(['(']) },
{ text: 'begin', length: 5, kind: 'OpeningBracket', bracketId: getKey('begin'), bracketIds: getImmutableSet(['begin']) },
{ text: 'case', length: 4, kind: 'OpeningBracket', bracketId: getKey('case'), bracketIds: getImmutableSet(['case']) },
{ text: 'casez', length: 5, kind: 'OpeningBracket', bracketId: getKey('casez'), bracketIds: getImmutableSet(['casez']) },
{ text: '\\left(', length: 6, kind: 'OpeningBracket', bracketId: getKey('\\left('), bracketIds: getImmutableSet(['\\left(']) },
{ text: '\\left.', length: 6, kind: 'OpeningBracket', bracketId: getKey('\\left.'), bracketIds: getImmutableSet(['\\left.']) },
{ text: '\\left[', length: 6, kind: 'OpeningBracket', bracketId: getKey('\\left['), bracketIds: getImmutableSet(['\\left[']) },
{ text: '}', length: 1, kind: 'ClosingBracket', bracketId: getKey('{'), bracketIds: getImmutableSet(['{']) },
{ text: ']', length: 1, kind: 'ClosingBracket', bracketId: getKey('['), bracketIds: getImmutableSet(['[']) },
{ text: ')', length: 1, kind: 'ClosingBracket', bracketId: getKey('('), bracketIds: getImmutableSet(['(']) },
{ text: 'end', length: 3, kind: 'ClosingBracket', bracketId: getKey('begin'), bracketIds: getImmutableSet(['begin']) },
{ text: 'endcase', length: 7, kind: 'ClosingBracket', bracketId: getKey('case'), bracketIds: getImmutableSet(['case', 'casez']) },
{ text: '\\right)', length: 7, kind: 'ClosingBracket', bracketId: getKey('\\left('), bracketIds: getImmutableSet(['\\left(', '\\left.']) },
{ text: '\\right.', length: 7, kind: 'ClosingBracket', bracketId: getKey('\\left('), bracketIds: getImmutableSet(['\\left(', '\\left[']) },
{ text: '\\right]', length: 7, kind: 'ClosingBracket', bracketId: getKey('\\left['), bracketIds: getImmutableSet(['\\left[', '\\left.']) }
];
const bracketsActual = bracketsExpected.map(x => tokenToObject(brackets.getToken(x.text, 3), x.text));
assert.deepStrictEqual(bracketsActual, bracketsExpected);
disposableStore.dispose();
});
});
function tokenToObject(token: Token | undefined, text: string): any {
if (token === undefined) {
return undefined;
}
return {
text: text,
length: token.length,
bracketId: token.bracketId,
bracketIds: token.bracketIds,
kind: {
[TokenKind.ClosingBracket]: 'ClosingBracket',
[TokenKind.OpeningBracket]: 'OpeningBracket',
[TokenKind.Text]: 'Text',
}[token.kind],
};
}

View file

@ -33,12 +33,12 @@ suite('Bracket Pair Colorizer - mergeItems', () => {
}
}
if (!node1.unopenedBrackets.equals(node2.unopenedBrackets)) {
if (!node1.missingBracketIds.equals(node2.missingBracketIds)) {
return false;
}
if (node1.kind === AstNodeKind.Pair && node2.kind === AstNodeKind.Pair) {
return node1.category === node2.category;
return true;
} else if (node1.kind === node2.kind) {
return true;
}

View file

@ -8,6 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairColorizer/brackets';
import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/model/bracketPairColorizer/length';
import { SmallImmutableSet, DenseKeyProvider } from 'vs/editor/common/model/bracketPairColorizer/smallImmutableSet';
import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairColorizer/tokenizer';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IState, ITokenizationSupport, LanguageId, LanguageIdentifier, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/modes';
@ -16,7 +17,17 @@ import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
suite('Bracket Pair Colorizer - Tokenizer', () => {
test('Basic', () => {
const mode1 = new LanguageIdentifier('testMode1', 2);
const languageId = 2;
const mode1 = new LanguageIdentifier('testMode1', languageId);
const denseKeyProvider = new DenseKeyProvider<string>();
const getImmutableSet = (elements: string[]) => {
let newSet = SmallImmutableSet.getEmpty();
elements.forEach(x => newSet = newSet.add(`${languageId}:::${x}`, denseKeyProvider));
return newSet;
};
const getKey = (value: string) => {
return denseKeyProvider.getKey(`${languageId}:::${value}`);
};
const tStandard = (text: string) => new TokenInfo(text, mode1.id, StandardTokenType.Other);
const tComment = (text: string) => new TokenInfo(text, mode1.id, StandardTokenType.Comment);
@ -28,10 +39,10 @@ suite('Bracket Pair Colorizer - Tokenizer', () => {
const disposableStore = new DisposableStore();
disposableStore.add(TokenizationRegistry.register(mode1.language, document.getTokenizationSupport()));
disposableStore.add(LanguageConfigurationRegistry.register(mode1, {
brackets: [['{', '}'], ['[', ']'], ['(', ')']],
brackets: [['{', '}'], ['[', ']'], ['(', ')'], ['begin', 'end']],
}));
const brackets = new LanguageAgnosticBracketTokens([['begin', 'end']]);
const brackets = new LanguageAgnosticBracketTokens(denseKeyProvider);
const model = createTextModel(document.getText(), {}, mode1);
model.forceTokenization(model.getLineCount());
@ -39,16 +50,16 @@ suite('Bracket Pair Colorizer - Tokenizer', () => {
const tokens = readAllTokens(new TextBufferTokenizer(model, brackets));
assert.deepStrictEqual(toArr(tokens, model), [
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2000, kind: 'OpeningBracket', languageId: 2, text: '{', },
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2000, kind: 'ClosingBracket', languageId: 2, text: '}', },
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2004, kind: 'OpeningBracket', languageId: 2, text: 'begin', },
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2004, kind: 'ClosingBracket', languageId: 2, text: 'end', },
{ category: -1, kind: 'Text', languageId: -1, text: '\nhello{', },
{ category: 2000, kind: 'ClosingBracket', languageId: 2, text: '}', }
{ bracketId: -1, bracketIds: getImmutableSet([]), kind: 'Text', text: ' ', },
{ bracketId: getKey('{'), bracketIds: getImmutableSet(['{']), kind: 'OpeningBracket', text: '{', },
{ bracketId: -1, bracketIds: getImmutableSet([]), kind: 'Text', text: ' ', },
{ bracketId: getKey('{'), bracketIds: getImmutableSet(['{']), kind: 'ClosingBracket', text: '}', },
{ bracketId: -1, bracketIds: getImmutableSet([]), kind: 'Text', text: ' ', },
{ bracketId: getKey('begin'), bracketIds: getImmutableSet(['begin']), kind: 'OpeningBracket', text: 'begin', },
{ bracketId: -1, bracketIds: getImmutableSet([]), kind: 'Text', text: ' ', },
{ bracketId: getKey('begin'), bracketIds: getImmutableSet(['begin']), kind: 'ClosingBracket', text: 'end', },
{ bracketId: -1, bracketIds: getImmutableSet([]), kind: 'Text', text: '\nhello{', },
{ bracketId: getKey('{'), bracketIds: getImmutableSet(['{']), kind: 'ClosingBracket', text: '}', }
]);
disposableStore.dispose();
@ -80,13 +91,13 @@ function toArr(tokens: Token[], model: TextModel): any[] {
function tokenToObj(token: Token, offset: Length, model: TextModel): any {
return {
text: model.getValueInRange(lengthsToRange(offset, lengthAdd(offset, token.length))),
category: token.category,
bracketId: token.bracketId,
bracketIds: token.bracketIds,
kind: {
[TokenKind.ClosingBracket]: 'ClosingBracket',
[TokenKind.OpeningBracket]: 'OpeningBracket',
[TokenKind.Text]: 'Text',
}[token.kind],
languageId: token.languageId,
}[token.kind]
};
}

View file

@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export function showHistoryKeybindingHint(keybindingService: IKeybindingService): boolean {
return keybindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && keybindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down';
}

View file

@ -290,7 +290,7 @@ export class DiskFileSystemProvider extends Disposable implements
// to flush the contents to disk if possible.
if (this.writeHandles.delete(fd) && this.canFlush) {
try {
await Promises.fdatasync(fd);
await Promises.fdatasync(fd); // https://github.com/microsoft/vscode/issues/9589
} catch (error) {
// In some exotic setups it is well possible that node fails to sync
// In that case we disable flushing and log the error to our logger

View file

@ -84,7 +84,6 @@ export const enum TerminalSettingId {
SplitCwd = 'terminal.integrated.splitCwd',
WindowsEnableConpty = 'terminal.integrated.windowsEnableConpty',
WordSeparators = 'terminal.integrated.wordSeparators',
TitleMode = 'terminal.integrated.titleMode',
EnableFileLinks = 'terminal.integrated.enableFileLinks',
UnicodeVersion = 'terminal.integrated.unicodeVersion',
ExperimentalLinkProvider = 'terminal.integrated.experimentalLinkProvider',
@ -95,7 +94,6 @@ export const enum TerminalSettingId {
PersistentSessionReviveProcess = 'terminal.integrated.persistentSessionReviveProcess',
CustomGlyphs = 'terminal.integrated.customGlyphs',
PersistentSessionScrollback = 'terminal.integrated.persistentSessionScrollback',
PersistentSessionExperimentalSerializer = 'terminal.integrated.persistentSessionExperimentalSerializer',
InheritEnv = 'terminal.integrated.inheritEnv',
ShowLinkHover = 'terminal.integrated.showLinkHover',
}
@ -560,7 +558,6 @@ export interface IReconnectConstants {
graceTime: number;
shortGraceTime: number;
scrollback: number;
useExperimentalSerialization: boolean;
}
export const enum LocalReconnectConstants {

View file

@ -366,12 +366,6 @@ const terminalPlatformConfiguration: IConfigurationNode = {
type: 'number',
default: 100
},
[TerminalSettingId.PersistentSessionExperimentalSerializer]: {
scope: ConfigurationScope.APPLICATION,
description: localize('terminal.integrated.persistentSessionExperimentalSerializer', "Whether to use a more efficient experimental approach for restoring the terminal's buffer. This setting requires a restart to take effect."),
type: 'boolean',
default: true
},
[TerminalSettingId.ShowLinkHover]: {
scope: ConfigurationScope.APPLICATION,
description: localize('terminal.integrated.showLinkHover', "Whether to show hovers for links in the terminal output."),

View file

@ -17,14 +17,7 @@ export interface IRemoteTerminalProcessReplayEvent {
events: ReplayEntry[];
}
export interface ITerminalSerializer {
handleData(data: string): void;
handleResize(cols: number, rows: number): void;
generateReplayEvent(normalBufferOnly?: boolean): Promise<IPtyHostProcessReplayEvent>;
setUnicodeVersion?(version: '6' | '11'): void;
}
export class TerminalRecorder implements ITerminalSerializer {
export class TerminalRecorder {
private _entries: RecorderEntry[];
private _totalDataLength: number = 0;

View file

@ -26,13 +26,11 @@ server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(h
const reconnectConstants: IReconnectConstants = {
graceTime: parseInt(process.env.VSCODE_RECONNECT_GRACE_TIME || '0'),
shortGraceTime: parseInt(process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME || '0'),
scrollback: parseInt(process.env.VSCODE_RECONNECT_SCROLLBACK || '100'),
useExperimentalSerialization: !!parseInt(process.env.VSCODE_RECONNECT_EXPERIMENTAL_SERIALIZATION || '1')
scrollback: parseInt(process.env.VSCODE_RECONNECT_SCROLLBACK || '100')
};
delete process.env.VSCODE_RECONNECT_GRACE_TIME;
delete process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME;
delete process.env.VSCODE_RECONNECT_SCROLLBACK;
delete process.env.VSCODE_RECONNECT_EXPERIMENTAL_SERIALIZATION;
const ptyService = new PtyService(lastPtyId, logService, reconnectConstants);
server.registerChannel(TerminalIpcChannels.PtyHost, ProxyChannel.fromService(ptyService));

View file

@ -121,8 +121,7 @@ export class PtyHostService extends Disposable implements IPtyService {
VSCODE_VERBOSE_LOGGING: 'true', // transmit console logs from server to client,
VSCODE_RECONNECT_GRACE_TIME: this._reconnectConstants.graceTime,
VSCODE_RECONNECT_SHORT_GRACE_TIME: this._reconnectConstants.shortGraceTime,
VSCODE_RECONNECT_SCROLLBACK: this._reconnectConstants.scrollback,
VSCODE_RECONNECT_EXPERIMENTAL_SERIALIZATION: this._reconnectConstants.useExperimentalSerialization ? 1 : 0
VSCODE_RECONNECT_SCROLLBACK: this._reconnectConstants.scrollback
}
}
);

View file

@ -19,7 +19,6 @@ import { Terminal as XtermTerminal } from 'xterm-headless';
import type { ISerializeOptions, SerializeAddon as XtermSerializeAddon } from 'xterm-addon-serialize';
import type { Unicode11Addon as XtermUnicode11Addon } from 'xterm-addon-unicode11';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto } from 'vs/platform/terminal/common/terminalProcess';
import { ITerminalSerializer, TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder';
import { getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment';
import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess';
import { localize } from 'vs/nls';
@ -471,18 +470,13 @@ export class PersistentTerminalProcess extends Disposable {
super();
this._logService.trace('persistentTerminalProcess#ctor', _persistentProcessId, arguments);
this._wasRevived = reviveBuffer !== undefined;
if (reconnectConstants.useExperimentalSerialization) {
this._serializer = new XtermSerializer(
cols,
rows,
reconnectConstants.scrollback,
unicodeVersion,
reviveBuffer
);
} else {
this._serializer = new TerminalRecorder(cols, rows);
}
this._serializer = new XtermSerializer(
cols,
rows,
reconnectConstants.scrollback,
unicodeVersion,
reviveBuffer
);
this._orphanQuestionBarrier = null;
this._orphanQuestionReplyTime = 0;
this._disconnectRunner1 = this._register(new ProcessTimeRunOnceScheduler(() => {
@ -782,3 +776,10 @@ export interface ISerializedTerminalState {
replayEvent: IPtyHostProcessReplayEvent;
timestamp: number;
}
export interface ITerminalSerializer {
handleData(data: string): void;
handleResize(cols: number, rows: number): void;
generateReplayEvent(normalBufferOnly?: boolean): Promise<IPtyHostProcessReplayEvent>;
setUnicodeVersion?(version: '6' | '11'): void;
}

View file

@ -151,7 +151,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
this._initialCwd = cwd;
this._properties[ProcessPropertyType.InitialCwd] = this._initialCwd;
this._properties[ProcessPropertyType.Cwd] = this._initialCwd;
const useConpty = windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309;
this._ptyOptions = {
name,
@ -164,6 +163,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
// This option will force conpty to not redraw the whole viewport on launch
conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText
};
const osRelease = os.release().split('.');
// Delay resizes to avoid conpty not respecting very early resize calls
if (isWindows) {
if (useConpty && cols === 0 && rows === 0 && this.shellLaunchConfig.executable?.endsWith('Git\\bin\\bash.exe')) {
@ -182,7 +182,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
this._register(this._windowsShellHelper.onShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e)));
this._register(this._windowsShellHelper.onShellNameChanged(e => this._onProcessTitleChanged.fire(e)));
});
} else {
} else if (isLinux || (osRelease.length > 0 && parseInt(osRelease[0]) < 20)) {
this.capabilities.push(ProcessCapability.CwdDetection);
}
}

31
src/vs/vscode.d.ts vendored
View file

@ -5858,6 +5858,11 @@ declare module 'vscode' {
*/
readonly exitStatus: TerminalExitStatus | undefined;
/**
* The current state of the {@link Terminal}.
*/
readonly state: TerminalState;
/**
* Send text to the terminal. The text is written to the stdin of the underlying pty process
* (shell) of the terminal.
@ -5887,6 +5892,27 @@ declare module 'vscode' {
dispose(): void;
}
/**
* Represents the state of a {@link Terminal}.
*/
export interface TerminalState {
/**
* Whether the {@link Terminal} has been interacted with. Interaction means that the
* terminal has sent data to the process which depending on the terminal's _mode_. By
* default input is sent when a key is pressed or when a command or extension sends text,
* but based on the terminal's mode it can also happen on:
*
* - a pointer click event
* - a pointer scroll event
* - a pointer move event
* - terminal focus in/out
*
* For more information on events that can send data see "DEC Private Mode Set (DECSET)" on
* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
*/
readonly isInteractedWith: boolean;
}
/**
* Provides information on a line in a terminal in order to provide links for it.
*/
@ -8662,6 +8688,11 @@ declare module 'vscode' {
*/
export const onDidCloseTerminal: Event<Terminal>;
/**
* An {@link Event} which fires when a {@link Terminal.state terminal's state} has changed.
*/
export const onDidChangeTerminalState: Event<Terminal>;
/**
* Represents the current window's state.
*/

View file

@ -906,46 +906,6 @@ declare module 'vscode' {
//#endregion
//#region Terminal state event https://github.com/microsoft/vscode/issues/127717
/**
* Represents the state of a {@link Terminal}.
*/
export interface TerminalState {
/**
* Whether the {@link Terminal} has been interacted with. Interaction means that the
* terminal has sent data to the process which depending on the terminal's _mode_. By
* default input is sent when a key is pressed or when a command or extension sends text,
* but based on the terminal's mode it can also happen on:
*
* - a pointer click event
* - a pointer scroll event
* - a pointer move event
* - terminal focus in/out
*
* For more information on events that can send data see "DEC Private Mode Set (DECSET)" on
* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
*/
// todo@API Maybe, isInteractedWith to align with other isXYZ
readonly interactedWith: boolean;
}
export interface Terminal {
/**
* The current state of the {@link Terminal}.
*/
readonly state: TerminalState;
}
export namespace window {
/**
* An {@link Event} which fires when a {@link Terminal.state terminal's state} has changed.
*/
export const onDidChangeTerminalState: Event<Terminal>;
}
//#endregion
//#region Terminal location https://github.com/microsoft/vscode/issues/45407
export interface TerminalOptions {

View file

@ -21,7 +21,6 @@ export class MainThreadEditorTabs {
private readonly _proxy: IExtHostEditorTabsShape;
private readonly _tabModel: Map<number, IEditorTabDto[]> = new Map<number, IEditorTabDto[]>();
private _currentlyActiveTab: { groupId: number, tab: IEditorTabDto } | undefined = undefined;
private _oldTabModel: IEditorTabDto[] = [];
constructor(
extHostContext: IExtHostContext,
@ -111,7 +110,7 @@ export class MainThreadEditorTabs {
if (movedTab === undefined) {
return;
}
this._tabModel.get(event.groupId)?.splice(event.oldEditorIndex, 0, movedTab[0]);
this._tabModel.get(event.groupId)?.splice(event.editorIndex, 0, movedTab[0]);
movedTab[0].isActive = (this._editorGroupsService.activeGroup.id === event.groupId) && this._editorGroupsService.activeGroup.isActive({ resource: URI.revive(movedTab[0].resource), options: { override: movedTab[0].editorId } });
// Update the currently active tab
if (movedTab[0].isActive) {
@ -123,7 +122,7 @@ export class MainThreadEditorTabs {
}
private _onDidGroupActivate(event: IEditorsChangeEvent): void {
if (event.kind !== GroupChangeKind.GROUP_INDEX) {
if (event.kind !== GroupChangeKind.GROUP_INDEX && event.kind !== GroupChangeKind.EDITOR_ACTIVE) {
return;
}
this._findAndUpdateActiveTab();
@ -146,22 +145,26 @@ export class MainThreadEditorTabs {
}, this);
}
/**
* Helper function to compare previous tab model to current one
* @param current The current tab model to compare to the previous mode
* @returns True if they're equivalent, false otherwise
*/
private _compareTabsModel(current: IEditorTabDto[]): boolean {
if (this._oldTabModel.length !== current.length) {
return false;
}
for (let i = 0; i < current.length; i++) {
if (this._oldTabModel[i].resource !== current[i].resource && this._oldTabModel[i].editorId !== current[i].editorId) {
return false;
}
}
return true;
}
// TODOD @lramos15 Remove this after done finishing the tab model code
// private _eventArrayToString(events: IEditorsChangeEvent[]): void {
// let eventString = '[';
// events.forEach(event => {
// switch (event.kind) {
// case GroupChangeKind.GROUP_INDEX: eventString += 'GROUP_INDEX, '; break;
// case GroupChangeKind.EDITOR_ACTIVE: eventString += 'EDITOR_ACTIVE, '; break;
// case GroupChangeKind.EDITOR_PIN: eventString += 'EDITOR_PIN, '; break;
// case GroupChangeKind.EDITOR_OPEN: eventString += 'EDITOR_OPEN, '; break;
// case GroupChangeKind.EDITOR_CLOSE: eventString += 'EDITOR_CLOSE, '; break;
// case GroupChangeKind.EDITOR_MOVE: eventString += 'EDITOR_MOVE, '; break;
// case GroupChangeKind.EDITOR_LABEL: eventString += 'EDITOR_LABEL, '; break;
// case GroupChangeKind.GROUP_ACTIVE: eventString += 'GROUP_ACTIVE, '; break;
// case GroupChangeKind.GROUP_LOCKED: eventString += 'GROUP_LOCKED, '; break;
// default: eventString += 'UNKNOWN, '; break;
// }
// });
// eventString += ']';
// console.log(eventString);
// }
private _updateTabsModel(events: IEditorsChangeEvent[]): void {
events.forEach(event => {
@ -173,6 +176,7 @@ export class MainThreadEditorTabs {
case GroupChangeKind.EDITOR_CLOSE:
this._onDidTabClose(event);
break;
case GroupChangeKind.EDITOR_ACTIVE:
case GroupChangeKind.GROUP_ACTIVE:
if (this._editorGroupsService.activeGroup.id !== event.groupId) {
return;
@ -193,9 +197,6 @@ export class MainThreadEditorTabs {
// Flatten the map into a singular array to send the ext host
let allTabs: IEditorTabDto[] = [];
this._tabModel.forEach((tabs) => allTabs = allTabs.concat(tabs));
if (!this._compareTabsModel(allTabs)) {
this._proxy.$acceptEditorTabs(allTabs);
this._oldTabModel = allTabs;
}
this._proxy.$acceptEditorTabs(allTabs);
}
}

View file

@ -157,7 +157,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData));
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService, extHostFileSystemInfo));
const extHostLanguages = rpcProtocol.set(ExtHostContext.ExtHostLanguages, new ExtHostLanguages(rpcProtocol, extHostDocuments, extHostCommands.converter));
const extHostLanguages = rpcProtocol.set(ExtHostContext.ExtHostLanguages, new ExtHostLanguages(rpcProtocol, extHostDocuments, extHostCommands.converter, uriTransformer));
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation));
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors));

View file

@ -17,7 +17,7 @@ import * as extHostProtocol from './extHost.protocol';
import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
import { isFalsyOrEmpty, isNonEmptyArray, coalesce, asArray } from 'vs/base/common/arrays';
import { isFalsyOrEmpty, isNonEmptyArray, coalesce } from 'vs/base/common/arrays';
import { isArray, isObject } from 'vs/base/common/types';
import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { ILogService } from 'vs/platform/log/common/log';
@ -1522,7 +1522,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
private static _handlePool: number = 0;
private readonly _uriTransformer: IURITransformer | null;
private readonly _uriTransformer: IURITransformer;
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
private _documents: ExtHostDocuments;
private _commands: ExtHostCommands;
@ -1533,7 +1533,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
constructor(
mainContext: extHostProtocol.IMainContext,
uriTransformer: IURITransformer | null,
uriTransformer: IURITransformer,
documents: ExtHostDocuments,
commands: ExtHostCommands,
diagnostics: ExtHostDiagnostics,
@ -1550,35 +1550,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
}
private _transformDocumentSelector(selector: vscode.DocumentSelector): Array<extHostProtocol.IDocumentFilterDto> {
return coalesce(asArray(selector).map(sel => this._doTransformDocumentSelector(sel)));
}
private _doTransformDocumentSelector(selector: string | vscode.DocumentFilter): extHostProtocol.IDocumentFilterDto | undefined {
if (typeof selector === 'string') {
return {
$serialized: true,
language: selector
};
}
if (selector) {
return {
$serialized: true,
language: selector.language,
scheme: this._transformScheme(selector.scheme),
pattern: typeof selector.pattern === 'undefined' ? undefined : typeConvert.GlobPattern.from(selector.pattern),
exclusive: selector.exclusive
};
}
return undefined;
}
private _transformScheme(scheme: string | undefined): string | undefined {
if (this._uriTransformer && typeof scheme === 'string') {
return this._uriTransformer.transformOutgoingScheme(scheme);
}
return scheme;
return typeConvert.DocumentSelector.from(selector, this._uriTransformer);
}
private _createDisposable(handle: number): Disposable {

View file

@ -13,6 +13,7 @@ import { disposableTimeout } from 'vs/base/common/async';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { IURITransformer } from 'vs/base/common/uriIpc';
export class ExtHostLanguages implements ExtHostLanguagesShape {
@ -23,7 +24,8 @@ export class ExtHostLanguages implements ExtHostLanguagesShape {
constructor(
mainContext: IMainContext,
private readonly _documents: ExtHostDocuments,
private readonly _commands: CommandsConverter
private readonly _commands: CommandsConverter,
private readonly _uriTransformer: IURITransformer | undefined
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadLanguages);
}
@ -108,7 +110,7 @@ export class ExtHostLanguages implements ExtHostLanguagesShape {
id: fullyQualifiedId,
name: data.name ?? extension.displayName ?? extension.name,
source: extension.displayName ?? extension.name,
selector: data.selector,
selector: typeConvert.DocumentSelector.from(data.selector, this._uriTransformer),
label: data.text,
detail: data.detail ?? '',
severity: data.severity === LanguageStatusSeverity.Error ? Severity.Error : data.severity === LanguageStatusSeverity.Warning ? Severity.Warning : Severity.Info,

View file

@ -68,7 +68,7 @@ export class ExtHostTerminal {
private _pidPromiseComplete: ((value: number | undefined) => any) | undefined;
private _rows: number | undefined;
private _exitStatus: vscode.TerminalExitStatus | undefined;
private _state: vscode.TerminalState = { interactedWith: false };
private _state: vscode.TerminalState = { isInteractedWith: false };
public isOpen: boolean = false;
@ -218,8 +218,8 @@ export class ExtHostTerminal {
}
public setInteractedWith(): boolean {
if (!this._state.interactedWith) {
this._state = { interactedWith: true };
if (!this._state.isInteractedWith) {
this._state = { isInteractedWith: true };
return true;
}
return false;

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
import { asArray, coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
import { VSBuffer } from 'vs/base/common/buffer';
import * as htmlContent from 'vs/base/common/htmlContent';
import { DisposableStore } from 'vs/base/common/lifecycle';
@ -12,6 +12,7 @@ import { parse } from 'vs/base/common/marshalling';
import { cloneAndChange } from 'vs/base/common/objects';
import { isDefined, isEmptyObject, isNumber, isString } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
import { IPosition } from 'vs/editor/common/core/position';
import * as editorRange from 'vs/editor/common/core/range';
@ -122,6 +123,41 @@ export namespace Position {
}
}
export namespace DocumentSelector {
export function from(value: vscode.DocumentSelector, uriTransformer?: IURITransformer): extHostProtocol.IDocumentFilterDto[] {
return coalesce(asArray(value).map(sel => _doTransformDocumentSelector(sel, uriTransformer)));
}
function _doTransformDocumentSelector(selector: string | vscode.DocumentFilter, uriTransformer: IURITransformer | undefined): extHostProtocol.IDocumentFilterDto | undefined {
if (typeof selector === 'string') {
return {
$serialized: true,
language: selector
};
}
if (selector) {
return {
$serialized: true,
language: selector.language,
scheme: _transformScheme(selector.scheme, uriTransformer),
pattern: typeof selector.pattern === 'undefined' ? undefined : GlobPattern.from(selector.pattern),
exclusive: selector.exclusive
};
}
return undefined;
}
function _transformScheme(scheme: string | undefined, uriTransformer: IURITransformer | undefined): string | undefined {
if (uriTransformer && typeof scheme === 'string') {
return uriTransformer.transformOutgoingScheme(scheme);
}
return scheme;
}
}
export namespace DiagnosticTag {
export function from(value: vscode.DiagnosticTag): MarkerTag | undefined {
switch (value) {

View file

@ -64,6 +64,7 @@ registerThemingParticipant((theme, collector) => {
const CONTEXT_BANNER_FOCUSED = new RawContextKey<boolean>('bannerFocused', false, localize('bannerFocused', "Whether the banner has keyboard focus"));
export class BannerPart extends Part implements IBannerService {
declare readonly _serviceBrand: undefined;
// #region IView
@ -80,8 +81,7 @@ export class BannerPart extends Part implements IBannerService {
return this.visible ? this.height : 0;
}
private _onDidChangeSize = new Emitter<{ width: number; height: number; } | undefined>();
private _onDidChangeSize = this._register(new Emitter<{ width: number; height: number; } | undefined>());
override get onDidChange() { return this._onDidChangeSize.event; }
//#endregion

View file

@ -36,7 +36,7 @@ import {
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction,
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReOpenInTextEditorAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction, ToggleEditorTypeAction
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReOpenInTextEditorAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction, ToggleEditorTypeAction, SplitEditorToAboveGroupAction, SplitEditorToBelowGroupAction, SplitEditorToFirstGroupAction, SplitEditorToLastGroupAction, SplitEditorToLeftGroupAction, SplitEditorToNextGroupAction, SplitEditorToPreviousGroupAction, SplitEditorToRightGroupAction
} from 'vs/workbench/browser/parts/editor/editorActions';
import {
CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID,
@ -218,6 +218,14 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToLeftGroup
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToRightGroupAction), 'View: Move Editor into Right Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToAboveGroupAction), 'View: Move Editor into Above Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToBelowGroupAction), 'View: Move Editor into Below Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToPreviousGroupAction), 'View: Split Editor into Previous Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToNextGroupAction), 'View: Split Editor into Next Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToFirstGroupAction), 'View: Split Editor into First Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToLastGroupAction), 'View: Split Editor into Last Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToLeftGroupAction), 'View: Split Editor into Left Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToRightGroupAction), 'View: Split Editor into Right Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToAboveGroupAction), 'View: Split Editor into Above Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SplitEditorToBelowGroupAction), 'View: Split Editor into Below Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusActiveGroupAction), 'View: Focus Active Editor Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusFirstGroupAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusLastGroupAction), 'View: Focus Last Editor Group', CATEGORIES.View.value);

View file

@ -13,7 +13,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -1608,7 +1608,7 @@ export class MoveEditorLeftInGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'left' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'left' } as ActiveEditorMoveCopyArguments);
}
}
@ -1622,7 +1622,7 @@ export class MoveEditorRightInGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'right' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'right' } as ActiveEditorMoveCopyArguments);
}
}
@ -1636,7 +1636,7 @@ export class MoveEditorToPreviousGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'previous', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'previous', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
@ -1650,7 +1650,7 @@ export class MoveEditorToNextGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'next', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'next', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
@ -1664,7 +1664,7 @@ export class MoveEditorToAboveGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'up', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'up', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
@ -1678,7 +1678,7 @@ export class MoveEditorToBelowGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'down', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'down', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
@ -1692,7 +1692,7 @@ export class MoveEditorToLeftGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'left', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'left', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
@ -1706,7 +1706,7 @@ export class MoveEditorToRightGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'right', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'right', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
@ -1720,7 +1720,7 @@ export class MoveEditorToFirstGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'first', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'first', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
@ -1734,7 +1734,119 @@ export class MoveEditorToLastGroupAction extends ExecuteCommandAction {
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'last', by: 'group' } as ActiveEditorMoveArguments);
super(id, label, MOVE_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'last', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToPreviousGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToPreviousGroup';
static readonly LABEL = localize('splitEditorToPreviousGroup', "Split Editor into Previous Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'previous', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToNextGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToNextGroup';
static readonly LABEL = localize('splitEditorToNextGroup', "Split Editor into Next Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'next', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToAboveGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToAboveGroup';
static readonly LABEL = localize('splitEditorToAboveGroup', "Split Editor into Above Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'up', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToBelowGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToBelowGroup';
static readonly LABEL = localize('splitEditorToBelowGroup', "Split Editor into Below Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'down', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToLeftGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToLeftGroup';
static readonly LABEL = localize('splitEditorToLeftGroup', "Split Editor into Left Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'left', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToRightGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToRightGroup';
static readonly LABEL = localize('splitEditorToRightGroup', "Split Editor into Right Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'right', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToFirstGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToFirstGroup';
static readonly LABEL = localize('splitEditorToFirstGroup', "Split Editor into First Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'first', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}
export class SplitEditorToLastGroupAction extends ExecuteCommandAction {
static readonly ID = 'workbench.action.splitEditorToLastGroup';
static readonly LABEL = localize('splitEditorToLastGroup', "Split Editor into Last Group");
constructor(
id: string,
label: string,
@ICommandService commandService: ICommandService
) {
super(id, label, COPY_ACTIVE_EDITOR_COMMAND_ID, commandService, { to: 'last', by: 'group' } as ActiveEditorMoveCopyArguments);
}
}

View file

@ -31,6 +31,7 @@ import { EditorResolution, IEditorOptions, ITextEditorOptions } from 'vs/platfor
import { Schemas } from 'vs/base/common/network';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
@ -42,6 +43,7 @@ export const CLOSE_EDITOR_GROUP_COMMAND_ID = 'workbench.action.closeGroup';
export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors';
export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor';
export const COPY_ACTIVE_EDITOR_COMMAND_ID = 'copyActiveEditor';
export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
export const TOGGLE_KEEP_EDITORS_COMMAND_ID = 'workbench.action.toggleKeepEditors';
@ -87,13 +89,13 @@ export const API_OPEN_EDITOR_COMMAND_ID = '_workbench.open';
export const API_OPEN_DIFF_EDITOR_COMMAND_ID = '_workbench.diff';
export const API_OPEN_WITH_EDITOR_COMMAND_ID = '_workbench.openWith';
export interface ActiveEditorMoveArguments {
export interface ActiveEditorMoveCopyArguments {
to: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
by: 'tab' | 'group';
value: number;
}
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
const isActiveEditorMoveCopyArg = function (arg: ActiveEditorMoveCopyArguments): boolean {
if (!isObject(arg)) {
return false;
}
@ -113,145 +115,174 @@ const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean
return true;
};
function registerActiveEditorMoveCommand(): void {
function registerActiveEditorMoveCopyCommand(): void {
const moveCopyJSONSchema: IJSONSchema = {
'type': 'object',
'required': ['to'],
'properties': {
'to': {
'type': 'string',
'enum': ['left', 'right']
},
'by': {
'type': 'string',
'enum': ['tab', 'group']
},
'value': {
'type': 'number'
}
}
};
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: EditorContextKeys.editorTextFocus,
primary: 0,
handler: (accessor, args) => moveActiveEditor(args, accessor),
handler: (accessor, args) => moveCopyActiveEditor(true, args, accessor),
description: {
description: localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
args: [
{
name: localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
description: localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."),
constraint: isActiveEditorMoveArg,
schema: {
'type': 'object',
'required': ['to'],
'properties': {
'to': {
'type': 'string',
'enum': ['left', 'right']
},
'by': {
'type': 'string',
'enum': ['tab', 'group']
},
'value': {
'type': 'number'
}
},
}
constraint: isActiveEditorMoveCopyArg,
schema: moveCopyJSONSchema
}
]
}
});
}
function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), accessor: ServicesAccessor): void {
args.to = args.to || 'right';
args.by = args.by || 'tab';
args.value = typeof args.value === 'number' ? args.value : 1;
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: COPY_ACTIVE_EDITOR_COMMAND_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: EditorContextKeys.editorTextFocus,
primary: 0,
handler: (accessor, args) => moveCopyActiveEditor(false, args, accessor),
description: {
description: localize('editorCommand.activeEditorCopy.description', "Copy the active editor by groups"),
args: [
{
name: localize('editorCommand.activeEditorCopy.arg.name', "Active editor copy argument"),
description: localize('editorCommand.activeEditorCopy.arg.description', "Argument Properties:\n\t* 'to': String value providing where to copy.\n\t* 'value': Number value providing how many positions or an absolute position to copy."),
constraint: isActiveEditorMoveCopyArg,
schema: moveCopyJSONSchema
}
]
}
});
const activeEditorPane = accessor.get(IEditorService).activeEditorPane;
if (activeEditorPane) {
switch (args.by) {
case 'tab':
return moveActiveTab(args, activeEditorPane, accessor);
case 'group':
return moveActiveEditorToGroup(args, activeEditorPane, accessor);
function moveCopyActiveEditor(isMove: boolean, args: ActiveEditorMoveCopyArguments = Object.create(null), accessor: ServicesAccessor): void {
args.to = args.to || 'right';
args.by = args.by || 'tab';
args.value = typeof args.value === 'number' ? args.value : 1;
const activeEditorPane = accessor.get(IEditorService).activeEditorPane;
if (activeEditorPane) {
switch (args.by) {
case 'tab':
if (isMove) {
return moveActiveTab(args, activeEditorPane);
}
break;
case 'group':
return moveCopyActiveEditorToGroup(isMove, args, activeEditorPane, accessor);
}
}
}
}
function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void {
const group = control.group;
let index = group.getIndexOfEditor(control.input);
switch (args.to) {
case 'first':
index = 0;
break;
case 'last':
index = group.count - 1;
break;
case 'left':
index = index - args.value;
break;
case 'right':
index = index + args.value;
break;
case 'center':
index = Math.round(group.count / 2) - 1;
break;
case 'position':
index = args.value - 1;
break;
function moveActiveTab(args: ActiveEditorMoveCopyArguments, control: IVisibleEditorPane): void {
const group = control.group;
let index = group.getIndexOfEditor(control.input);
switch (args.to) {
case 'first':
index = 0;
break;
case 'last':
index = group.count - 1;
break;
case 'left':
index = index - args.value;
break;
case 'right':
index = index + args.value;
break;
case 'center':
index = Math.round(group.count / 2) - 1;
break;
case 'position':
index = args.value - 1;
break;
}
index = index < 0 ? 0 : index >= group.count ? group.count - 1 : index;
group.moveEditor(control.input, group, { index });
}
index = index < 0 ? 0 : index >= group.count ? group.count - 1 : index;
group.moveEditor(control.input, group, { index });
}
function moveCopyActiveEditorToGroup(isMove: boolean, args: ActiveEditorMoveCopyArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void {
const editorGroupService = accessor.get(IEditorGroupsService);
const configurationService = accessor.get(IConfigurationService);
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void {
const editorGroupService = accessor.get(IEditorGroupsService);
const configurationService = accessor.get(IConfigurationService);
const sourceGroup = control.group;
let targetGroup: IEditorGroup | undefined;
const sourceGroup = control.group;
let targetGroup: IEditorGroup | undefined;
switch (args.to) {
case 'left':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.LEFT);
}
break;
case 'right':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.RIGHT);
}
break;
case 'up':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.UP }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.UP);
}
break;
case 'down':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.DOWN);
}
break;
case 'first':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.FIRST }, sourceGroup);
break;
case 'last':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.LAST }, sourceGroup);
break;
case 'previous':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.PREVIOUS }, sourceGroup);
break;
case 'next':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.NEXT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService));
}
break;
case 'center':
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1];
break;
case 'position':
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[args.value - 1];
break;
}
switch (args.to) {
case 'left':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.LEFT);
if (targetGroup) {
if (isMove) {
sourceGroup.moveEditor(control.input, targetGroup);
} else if (sourceGroup.id !== targetGroup.id) {
sourceGroup.copyEditor(control.input, targetGroup);
}
break;
case 'right':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.RIGHT);
}
break;
case 'up':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.UP }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.UP);
}
break;
case 'down':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.DOWN);
}
break;
case 'first':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.FIRST }, sourceGroup);
break;
case 'last':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.LAST }, sourceGroup);
break;
case 'previous':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.PREVIOUS }, sourceGroup);
break;
case 'next':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.NEXT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService));
}
break;
case 'center':
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1];
break;
case 'position':
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[args.value - 1];
break;
}
if (targetGroup) {
sourceGroup.moveEditor(control.input, targetGroup);
targetGroup.focus();
targetGroup.focus();
}
}
}
@ -1388,7 +1419,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon
}
export function setup(): void {
registerActiveEditorMoveCommand();
registerActiveEditorMoveCopyCommand();
registerEditorGroupsLayoutCommand();
registerDiffEditorCommands();
registerOpenEditorAPICommands();

View file

@ -155,7 +155,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
return isDirty ? localize('entryAriaLabelDirty', "{0}, dirty", nameAndDescription) : nameAndDescription;
})(),
description: editor.getDescription(),
description,
iconClasses: getIconClasses(this.modelService, this.modeService, resource).concat(editor.getLabelExtraClasses()),
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor),
buttons: (() => {

View file

@ -78,13 +78,13 @@ export class NoTabsTitleControl extends TitleControl {
this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleTap(e)));
// Context Menu
[EventType.CONTEXT_MENU, TouchEventType.Contextmenu].forEach(event => {
for (const event of [EventType.CONTEXT_MENU, TouchEventType.Contextmenu]) {
this._register(addDisposableListener(titleContainer, event, e => {
if (this.group.activeEditor) {
this.onContextMenu(this.group.activeEditor, e, titleContainer);
}
}));
});
}
}
private onTitleLabelClick(e: MouseEvent): void {

View file

@ -6,7 +6,7 @@
import 'vs/css!./media/tabstitlecontrol';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { shorten } from 'vs/base/common/labels';
import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { computeEditorAriaLabel } from 'vs/workbench/browser/editor';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@ -40,7 +40,7 @@ import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/pa
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
import { IFileService } from 'vs/platform/files/common/files';
import { withNullAsUndefined, assertAllDefined, assertIsDefined } from 'vs/base/common/types';
import { assertAllDefined, assertIsDefined } from 'vs/base/common/types';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { basenameOrAuthority } from 'vs/base/common/resources';
import { RunOnceScheduler } from 'vs/base/common/async';
@ -56,11 +56,12 @@ import { UNLOCK_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/edito
interface IEditorInputLabel {
name?: string;
description?: string;
forceDescription?: boolean;
title?: string;
ariaLabel?: string;
}
type AugmentedLabel = IEditorInputLabel & { editor: EditorInput };
type IEditorInputLabelAndEditor = IEditorInputLabel & { editor: EditorInput };
export class TabsTitleControl extends TitleControl {
@ -218,7 +219,7 @@ export class TabsTitleControl extends TitleControl {
}));
// New file when double clicking on tabs container (but not tabs)
[TouchEventType.Tap, EventType.DBLCLICK].forEach(eventType => {
for (const eventType of [TouchEventType.Tap, EventType.DBLCLICK]) {
this._register(addDisposableListener(tabsContainer, eventType, (e: MouseEvent | GestureEvent) => {
if (eventType === EventType.DBLCLICK) {
if (e.target !== tabsContainer) {
@ -245,7 +246,7 @@ export class TabsTitleControl extends TitleControl {
}
}, this.group.id);
}));
});
}
// Prevent auto-scrolling (https://github.com/microsoft/vscode/issues/16690)
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, e => {
@ -784,7 +785,7 @@ export class TabsTitleControl extends TitleControl {
}));
// Double click: either pin or toggle maximized
[TouchEventType.Tap, EventType.DBLCLICK].forEach(eventType => {
for (const eventType of [TouchEventType.Tap, EventType.DBLCLICK]) {
disposables.add(addDisposableListener(tab, eventType, (e: MouseEvent | GestureEvent) => {
if (eventType === EventType.DBLCLICK) {
EventHelper.stop(e);
@ -799,7 +800,7 @@ export class TabsTitleControl extends TitleControl {
this.group.pinEditor(editor);
}
}));
});
}
// Context menu
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, e => {
@ -953,11 +954,12 @@ export class TabsTitleControl extends TitleControl {
const { verbosity, shortenDuplicates } = this.getLabelConfigFlags(labelFormat);
// Build labels and descriptions for each editor
const labels = this.group.editors.map((editor, index) => ({
const labels: IEditorInputLabelAndEditor[] = this.group.editors.map((editor, index) => ({
editor,
name: editor.getName(),
description: editor.getDescription(verbosity),
title: withNullAsUndefined(editor.getTitle(Verbosity.LONG)),
forceDescription: editor.hasCapability(EditorInputCapabilities.ForceDescription),
title: editor.getTitle(Verbosity.LONG),
ariaLabel: computeEditorAriaLabel(editor, index, this.group, this.editorGroupService.count)
}));
@ -969,63 +971,68 @@ export class TabsTitleControl extends TitleControl {
this.tabLabels = labels;
}
private shortenTabLabels(labels: AugmentedLabel[]): void {
private shortenTabLabels(labels: IEditorInputLabelAndEditor[]): void {
// Gather duplicate titles, while filtering out invalid descriptions
const mapTitleToDuplicates = new Map<string, AugmentedLabel[]>();
const mapNameToDuplicates = new Map<string, IEditorInputLabelAndEditor[]>();
for (const label of labels) {
if (typeof label.description === 'string') {
getOrSet(mapTitleToDuplicates, label.name, []).push(label);
getOrSet(mapNameToDuplicates, label.name, []).push(label);
} else {
label.description = '';
}
}
// Identify duplicate titles and shorten descriptions
mapTitleToDuplicates.forEach(duplicateTitles => {
// Identify duplicate names and shorten descriptions
for (const [, duplicateLabels] of mapNameToDuplicates) {
// Remove description if the title isn't duplicated
if (duplicateTitles.length === 1) {
duplicateTitles[0].description = '';
// and we have no indication to enforce description
if (duplicateLabels.length === 1 && !duplicateLabels[0].forceDescription) {
duplicateLabels[0].description = '';
return;
continue;
}
// Identify duplicate descriptions
const mapDescriptionToDuplicates = new Map<string, AugmentedLabel[]>();
for (const label of duplicateTitles) {
getOrSet(mapDescriptionToDuplicates, label.description, []).push(label);
const mapDescriptionToDuplicates = new Map<string, IEditorInputLabelAndEditor[]>();
for (const duplicateLabel of duplicateLabels) {
getOrSet(mapDescriptionToDuplicates, duplicateLabel.description, []).push(duplicateLabel);
}
// For editors with duplicate descriptions, check whether any long descriptions differ
let useLongDescriptions = false;
mapDescriptionToDuplicates.forEach((duplicateDescriptions, name) => {
if (!useLongDescriptions && duplicateDescriptions.length > 1) {
const [first, ...rest] = duplicateDescriptions.map(({ editor }) => editor.getDescription(Verbosity.LONG));
for (const [, duplicateLabels] of mapDescriptionToDuplicates) {
if (!useLongDescriptions && duplicateLabels.length > 1) {
const [first, ...rest] = duplicateLabels.map(({ editor }) => editor.getDescription(Verbosity.LONG));
useLongDescriptions = rest.some(description => description !== first);
}
});
}
// If so, replace all descriptions with long descriptions
if (useLongDescriptions) {
mapDescriptionToDuplicates.clear();
duplicateTitles.forEach(label => {
label.description = label.editor.getDescription(Verbosity.LONG);
getOrSet(mapDescriptionToDuplicates, label.description, []).push(label);
});
for (const duplicateLabel of duplicateLabels) {
duplicateLabel.description = duplicateLabel.editor.getDescription(Verbosity.LONG);
getOrSet(mapDescriptionToDuplicates, duplicateLabel.description, []).push(duplicateLabel);
}
}
// Obtain final set of descriptions
const descriptions: string[] = [];
mapDescriptionToDuplicates.forEach((_, description) => descriptions.push(description));
for (const [description] of mapDescriptionToDuplicates) {
descriptions.push(description);
}
// Remove description if all descriptions are identical
// Remove description if all descriptions are identical unless forced
if (descriptions.length === 1) {
for (const label of mapDescriptionToDuplicates.get(descriptions[0]) || []) {
label.description = '';
if (!label.forceDescription) {
label.description = '';
}
}
return;
continue;
}
// Shorten descriptions
@ -1035,7 +1042,7 @@ export class TabsTitleControl extends TitleControl {
label.description = shortenedDescriptions[index];
}
});
});
}
}
private getLabelConfigFlags(value: string | undefined) {
@ -1096,21 +1103,21 @@ export class TabsTitleControl extends TitleControl {
// Settings
const tabActionsVisibility = isTabSticky && options.pinnedTabSizing === 'compact' ? 'off' /* treat sticky compact tabs as tabCloseButton: 'off' */ : options.tabCloseButton;
['off', 'left', 'right'].forEach(option => {
for (const option of ['off', 'left', 'right']) {
tabContainer.classList.toggle(`tab-actions-${option}`, tabActionsVisibility === option);
});
}
const tabSizing = isTabSticky && options.pinnedTabSizing === 'shrink' ? 'shrink' /* treat sticky shrink tabs as tabSizing: 'shrink' */ : options.tabSizing;
['fit', 'shrink'].forEach(option => {
for (const option of ['fit', 'shrink']) {
tabContainer.classList.toggle(`sizing-${option}`, tabSizing === option);
});
}
tabContainer.classList.toggle('has-icon', options.showIcons && options.hasIcons);
tabContainer.classList.toggle('sticky', isTabSticky);
['normal', 'compact', 'shrink'].forEach(option => {
for (const option of ['normal', 'compact', 'shrink']) {
tabContainer.classList.toggle(`sticky-${option}`, isTabSticky && options.pinnedTabSizing === option);
});
}
// Sticky compact/shrink tabs need a position to remain at their location
// when scrolling to stay in view (requirement for position: sticky)

View file

@ -44,6 +44,10 @@ export class StatusbarEntryItem extends Disposable {
return assertIsDefined(this.entry).name;
}
get hasCommand(): boolean {
return typeof this.entry?.command !== 'undefined';
}
constructor(
private container: HTMLElement,
entry: IStatusbarEntry,

View file

@ -63,6 +63,7 @@ export function isStatusbarEntryLocation(thing: unknown): thing is IStatusbarEnt
export interface IStatusbarViewModelEntry {
readonly id: string;
readonly name: string;
readonly hasCommand: boolean;
readonly alignment: StatusbarAlignment;
readonly priority: IStatusbarEntryPriority;
readonly container: HTMLElement;

View file

@ -5,7 +5,7 @@
import 'vs/css!./media/statusbarpart';
import { localize } from 'vs/nls';
import { dispose } from 'vs/base/common/lifecycle';
import { DisposableStore, dispose, MutableDisposable } from 'vs/base/common/lifecycle';
import { Part } from 'vs/workbench/browser/part';
import { EventType as TouchEventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -95,6 +95,8 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
}(this.configurationService, this.hoverService);
private readonly compactEntriesDisposable = this._register(new MutableDisposable<DisposableStore>());
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@ -114,7 +116,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
private registerListeners(): void {
// Entry visibility changes
this._register(this.onDidChangeEntryVisibility(() => this.updateClasses()));
this._register(this.onDidChangeEntryVisibility(() => this.updateCompactEntries()));
// Workbench state changes
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
@ -176,6 +178,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
readonly labelContainer = item.labelContainer;
get name() { return item.name; }
get hasCommand() { return item.hasCommand; }
};
// Add to view model
@ -337,8 +340,8 @@ export class StatusbarPart extends Part implements IStatusbarService {
target.appendChild(entry.container);
}
// Update CSS classes
this.updateClasses();
// Update compact entries
this.updateCompactEntries();
}
private appendStatusbarEntry(entry: IStatusbarViewModelEntry): void {
@ -357,14 +360,14 @@ export class StatusbarPart extends Part implements IStatusbarService {
target.insertBefore(entry.container, entries[index + 1].container); // insert before next element otherwise
}
// Update CSS classes
this.updateClasses();
// Update compact entries
this.updateCompactEntries();
}
private updateClasses(): void {
private updateCompactEntries(): void {
const entries = this.viewModel.entries;
// Clear compact related CSS classes if any
// Find visible entries and clear compact related CSS classes if any
const mapIdToVisibleEntry = new Map<string, IStatusbarViewModelEntry>();
for (const entry of entries) {
if (!this.viewModel.isHidden(entry.id)) {
@ -374,21 +377,58 @@ export class StatusbarPart extends Part implements IStatusbarService {
entry.container.classList.remove('compact-left', 'compact-right');
}
// Update entries with compact related CSS classes as needed
// Figure out groups of entries with `compact` alignment
const compactEntryGroups = new Map<string, Set<IStatusbarViewModelEntry>>();
for (const entry of mapIdToVisibleEntry.values()) {
if (
isStatusbarEntryLocation(entry.priority.primary) && // entry references another entry as location
entry.priority.primary.compact // entry wants to be compact
) {
const location = mapIdToVisibleEntry.get(entry.priority.primary.id);
if (location) {
if (entry.priority.primary.alignment === StatusbarAlignment.LEFT) {
location.container.classList.add('compact-left');
entry.container.classList.add('compact-right');
} else {
location.container.classList.add('compact-right');
entry.container.classList.add('compact-left');
const locationId = entry.priority.primary.id;
const location = mapIdToVisibleEntry.get(locationId);
if (!location) {
continue; // skip if location does not exist
}
// Build a map of entries that are compact among each other
let compactEntryGroup = compactEntryGroups.get(locationId);
if (!compactEntryGroup) {
compactEntryGroup = new Set<IStatusbarViewModelEntry>([entry, location]);
compactEntryGroups.set(locationId, compactEntryGroup);
} else {
compactEntryGroup.add(entry);
}
// Adjust CSS classes to move compact items closer together
if (entry.priority.primary.alignment === StatusbarAlignment.LEFT) {
location.container.classList.add('compact-left');
entry.container.classList.add('compact-right');
} else {
location.container.classList.add('compact-right');
entry.container.classList.add('compact-left');
}
}
}
// Install mouse listeners to update hover feedback for
// all compact entries that belong to each other
const statusBarItemHoverBackground = this.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND)?.toString();
this.compactEntriesDisposable.value = new DisposableStore();
if (statusBarItemHoverBackground && this.theme.type !== ColorScheme.HIGH_CONTRAST) {
for (const [, compactEntryGroup] of compactEntryGroups) {
for (const compactEntry of compactEntryGroup) {
if (!compactEntry.hasCommand) {
continue; // only show hover feedback when we have a command
}
this.compactEntriesDisposable.value.add(addDisposableListener(compactEntry.labelContainer, EventType.MOUSE_OVER, () => {
compactEntryGroup.forEach(compactEntry => compactEntry.labelContainer.style.backgroundColor = statusBarItemHoverBackground);
}));
this.compactEntriesDisposable.value.add(addDisposableListener(compactEntry.labelContainer, EventType.MOUSE_OUT, () => {
compactEntryGroup.forEach(compactEntry => compactEntry.labelContainer.style.backgroundColor = '');
}));
}
}
}
@ -502,7 +542,8 @@ registerThemingParticipant((theme, collector) => {
const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
if (statusBarItemActiveBackground) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:active:not(.disabled) { background-color: ${statusBarItemActiveBackground}; }`);
// using !important for this rule to win over any background color that is set via JS code for compact items in a group
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:active:not(.disabled) { background-color: ${statusBarItemActiveBackground} !important; }`);
}
}

View file

@ -522,7 +522,15 @@ export const enum EditorInputCapabilities {
* Signals that the editor can split into 2 in the same
* editor group.
*/
CanSplitInGroup = 1 << 5
CanSplitInGroup = 1 << 5,
/**
* Signals that the editor wants it's description to be
* visible when presented to the user. By default, a UI
* component may decide to hide the description portion
* for brevity.
*/
ForceDescription = 1 << 6
}
export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceSideBySideEditorInput;

View file

@ -3,22 +3,32 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { AbstractSideBySideEditorInputSerializer, SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity, IEditorDescriptor, IEditorPane, GroupIdentifier, IResourceDiffEditorInput, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, isResourceDiffEditorInput, IDiffEditorInput, IResourceSideBySideEditorInput } from 'vs/workbench/common/editor';
import { TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity, IEditorDescriptor, IEditorPane, GroupIdentifier, IResourceDiffEditorInput, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, isResourceDiffEditorInput, IDiffEditorInput, IResourceSideBySideEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel';
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
import { localize } from 'vs/nls';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { dirname } from 'vs/base/common/resources';
import { ILabelService } from 'vs/platform/label/common/label';
import { IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { shorten } from 'vs/base/common/labels';
interface IDiffEditorInputLabels {
name: string;
shortDescription: string | undefined;
mediumDescription: string | undefined;
longDescription: string | undefined;
forceDescription: boolean;
shortTitle: string;
mediumTitle: string;
longTitle: string;
}
/**
* The base editor input for the diff editor. It is made up of two editor inputs, the original version
@ -36,66 +46,119 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito
return DEFAULT_EDITOR_ASSOCIATION.id;
}
override get capabilities(): EditorInputCapabilities {
let capabilities = super.capabilities;
// Force description capability depends on labels
if (this.labels.forceDescription) {
capabilities |= EditorInputCapabilities.ForceDescription;
}
return capabilities;
}
private cachedModel: DiffEditorModel | undefined = undefined;
private readonly labels = this.computeLabels();
constructor(
name: string | undefined,
description: string | undefined,
preferredName: string | undefined,
preferredDescription: string | undefined,
readonly original: EditorInput,
readonly modified: EditorInput,
private readonly forceOpenAsBinary: boolean | undefined,
@ILabelService private readonly labelService: ILabelService,
@IFileService private readonly fileService: IFileService,
@IEditorService editorService: IEditorService
) {
super(name, description, original, modified, editorService);
super(preferredName, preferredDescription, original, modified, editorService);
}
private computeLabels(): IDiffEditorInputLabels {
// Name
let name: string;
let forceDescription = false;
if (this.preferredName) {
name = this.preferredName;
} else {
const originalName = this.original.getName();
const modifiedName = this.modified.getName();
name = localize('sideBySideLabels', "{0} ↔ {1}", originalName, modifiedName);
// Enforce description when the names are identical
forceDescription = originalName === modifiedName;
}
// Description
let shortDescription: string | undefined;
let mediumDescription: string | undefined;
let longDescription: string | undefined;
if (this.preferredDescription) {
shortDescription = this.preferredDescription;
mediumDescription = this.preferredDescription;
longDescription = this.preferredDescription;
} else {
shortDescription = this.computeLabel(this.original.getDescription(Verbosity.SHORT), this.modified.getDescription(Verbosity.SHORT));
longDescription = this.computeLabel(this.original.getDescription(Verbosity.LONG), this.modified.getDescription(Verbosity.LONG));
// Medium Description: try to be verbose by computing
// a label that resembles the difference between the two
const originalMediumDescription = this.original.getDescription(Verbosity.MEDIUM);
const modifiedMediumDescription = this.modified.getDescription(Verbosity.MEDIUM);
if (originalMediumDescription && modifiedMediumDescription) {
const [shortenedOriginalMediumDescription, shortenedModifiedMediumDescription] = shorten([originalMediumDescription, modifiedMediumDescription]);
mediumDescription = this.computeLabel(shortenedOriginalMediumDescription, shortenedModifiedMediumDescription);
}
}
// Title
const shortTitle = this.computeLabel(this.original.getTitle(Verbosity.SHORT) ?? this.original.getName(), this.modified.getTitle(Verbosity.SHORT) ?? this.modified.getName(), ' ↔ ');
const mediumTitle = this.computeLabel(this.original.getTitle(Verbosity.MEDIUM) ?? this.original.getName(), this.modified.getTitle(Verbosity.MEDIUM) ?? this.modified.getName(), ' ↔ ');
const longTitle = this.computeLabel(this.original.getTitle(Verbosity.LONG) ?? this.original.getName(), this.modified.getTitle(Verbosity.LONG) ?? this.modified.getName(), ' ↔ ');
return { name, shortDescription, mediumDescription, longDescription, forceDescription, shortTitle, mediumTitle, longTitle };
}
private computeLabel(originalLabel: string, modifiedLabel: string, separator?: string): string;
private computeLabel(originalLabel: string | undefined, modifiedLabel: string | undefined, separator?: string): string | undefined;
private computeLabel(originalLabel: string | undefined, modifiedLabel: string | undefined, separator = ' - '): string | undefined {
if (!originalLabel || !modifiedLabel) {
return undefined;
}
if (originalLabel === modifiedLabel) {
return modifiedLabel;
}
return `${originalLabel}${separator}${modifiedLabel}`;
}
override getName(): string {
if (!this.name) {
// Craft a name from original and modified input that includes the
// relative path in case both sides have different parents and we
// compare file resources.
const fileResources = this.asFileResources();
if (fileResources && dirname(fileResources.original).path !== dirname(fileResources.modified).path) {
return `${this.labelService.getUriLabel(fileResources.original, { relative: true })} ↔ ${this.labelService.getUriLabel(fileResources.modified, { relative: true })}`;
}
return localize('sideBySideLabels', "{0} ↔ {1}", this.original.getName(), this.modified.getName());
}
return this.name;
return this.labels.name;
}
override getDescription(verbosity = Verbosity.MEDIUM): string | undefined {
if (typeof this.description !== 'string') {
// Pass the description of the modified side in case both original
// and modified input have the same parent and we compare file resources.
const fileResources = this.asFileResources();
if (fileResources && dirname(fileResources.original).path === dirname(fileResources.modified).path) {
return this.modified.getDescription(verbosity);
}
switch (verbosity) {
case Verbosity.SHORT:
return this.labels.shortDescription;
case Verbosity.LONG:
return this.labels.longDescription;
case Verbosity.MEDIUM:
default:
return this.labels.mediumDescription;
}
return this.description;
}
private asFileResources(): { original: URI, modified: URI } | undefined {
if (
this.original instanceof AbstractTextResourceEditorInput &&
this.modified instanceof AbstractTextResourceEditorInput &&
this.fileService.canHandleResource(this.original.preferredResource) &&
this.fileService.canHandleResource(this.modified.preferredResource)
) {
return {
original: this.original.preferredResource,
modified: this.modified.preferredResource
};
override getTitle(verbosity?: Verbosity): string {
switch (verbosity) {
case Verbosity.SHORT:
return this.labels.shortTitle;
case Verbosity.LONG:
return this.labels.longTitle;
default:
case Verbosity.MEDIUM:
return this.labels.mediumTitle;
}
return undefined;
}
override async resolve(): Promise<EditorModel> {
@ -184,7 +247,7 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito
export class DiffEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {
protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
protected createEditorInput(instantiationService: IInstantiationService, name: string | undefined, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
return instantiationService.createInstance(DiffEditorInput, name, description, secondaryInput, primaryInput, undefined);
}
}

View file

@ -99,13 +99,6 @@ export abstract class EditorInput extends AbstractEditorInput {
return `Editor ${this.typeId}`;
}
/**
* Returns the extra classes to apply to the label of this input.
*/
getLabelExtraClasses(): string[] {
return [];
}
/**
* Returns the display description of this input.
*/
@ -120,6 +113,13 @@ export abstract class EditorInput extends AbstractEditorInput {
return this.getName();
}
/**
* Returns the extra classes to apply to the label of this input.
*/
getLabelExtraClasses(): string[] {
return [];
}
/**
* Returns the aria label to be read out by a screen reader.
*/

View file

@ -60,8 +60,8 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
private hasIdenticalSides = this.primary.matches(this.secondary);
constructor(
protected readonly name: string | undefined,
protected readonly description: string | undefined,
protected readonly preferredName: string | undefined,
protected readonly preferredDescription: string | undefined,
readonly secondary: EditorInput,
readonly primary: EditorInput,
@IEditorService private readonly editorService: IEditorService
@ -91,31 +91,37 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
}
override getName(): string {
if (!this.name) {
if (this.hasIdenticalSides) {
return this.primary.getName(); // keep name concise when same editor is opened side by side
}
return localize('sideBySideLabels', "{0} - {1}", this.secondary.getName(), this.primary.getName());
const preferredName = this.getPreferredName();
if (preferredName) {
return preferredName;
}
return this.name;
if (this.hasIdenticalSides) {
return this.primary.getName(); // keep name concise when same editor is opened side by side
}
return localize('sideBySideLabels', "{0} - {1}", this.secondary.getName(), this.primary.getName());
}
override getLabelExtraClasses(): string[] {
if (this.hasIdenticalSides) {
return this.primary.getLabelExtraClasses();
}
return super.getLabelExtraClasses();
getPreferredName(): string | undefined {
return this.preferredName;
}
override getDescription(verbosity?: Verbosity): string | undefined {
const preferredDescription = this.getPreferredDescription();
if (preferredDescription) {
return preferredDescription;
}
if (this.hasIdenticalSides) {
return this.primary.getDescription(verbosity);
}
return this.description;
return super.getDescription(verbosity);
}
getPreferredDescription(): string | undefined {
return this.preferredDescription;
}
override getTitle(verbosity?: Verbosity): string {
@ -126,6 +132,14 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
return super.getTitle(verbosity);
}
override getLabelExtraClasses(): string[] {
if (this.hasIdenticalSides) {
return this.primary.getLabelExtraClasses();
}
return super.getLabelExtraClasses();
}
override getAriaLabel(): string {
if (this.hasIdenticalSides) {
return this.primary.getAriaLabel();
@ -154,7 +168,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
return editor;
}
return new SideBySideEditorInput(this.name, this.description, editor, editor, this.editorService);
return new SideBySideEditorInput(this.preferredName, this.preferredDescription, editor, editor, this.editorService);
}
override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | undefined> {
@ -163,7 +177,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
return editor;
}
return new SideBySideEditorInput(this.name, this.description, editor, editor, this.editorService);
return new SideBySideEditorInput(this.preferredName, this.preferredDescription, editor, editor, this.editorService);
}
override revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
@ -185,7 +199,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
if (isEditorInput(renameResult.editor)) {
return {
editor: new SideBySideEditorInput(this.name, this.description, renameResult.editor, renameResult.editor, this.editorService),
editor: new SideBySideEditorInput(this.preferredName, this.preferredDescription, renameResult.editor, renameResult.editor, this.editorService),
options: {
...renameResult.options,
viewState: findViewStateForEditor(this, group, this.editorService)
@ -196,8 +210,8 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
if (isResourceEditorInput(renameResult.editor)) {
return {
editor: {
label: this.name,
description: this.description,
label: this.preferredName,
description: this.preferredDescription,
primary: renameResult.editor,
secondary: renameResult.editor,
options: {
@ -222,8 +236,8 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
!isResourceSideBySideEditorInput(primaryResourceEditorInput) && !isResourceSideBySideEditorInput(secondaryResourceEditorInput)
) {
const untypedInput: IResourceSideBySideEditorInput = {
label: this.name,
description: this.description,
label: this.preferredName,
description: this.preferredDescription,
primary: primaryResourceEditorInput,
secondary: secondaryResourceEditorInput
};
@ -263,7 +277,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
// Register SideBySide/DiffEditor Input Serializer
interface ISerializedSideBySideEditorInput {
name: string;
name: string | undefined;
description: string | undefined;
primarySerialized: string;
@ -298,8 +312,8 @@ export abstract class AbstractSideBySideEditorInputSerializer implements IEditor
if (primarySerialized && secondarySerialized) {
const serializedEditorInput: ISerializedSideBySideEditorInput = {
name: input.getName(),
description: input.getDescription(),
name: input.getPreferredName(),
description: input.getPreferredDescription(),
primarySerialized: primarySerialized,
secondarySerialized: secondarySerialized,
primaryTypeId: input.primary.typeId,
@ -336,12 +350,12 @@ export abstract class AbstractSideBySideEditorInputSerializer implements IEditor
return [registry.getEditorSerializer(secondaryEditorInputTypeId), registry.getEditorSerializer(primaryEditorInputTypeId)];
}
protected abstract createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput;
protected abstract createEditorInput(instantiationService: IInstantiationService, name: string | undefined, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput;
}
export class SideBySideEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {
protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
protected createEditorInput(instantiationService: IInstantiationService, name: string | undefined, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
return instantiationService.createInstance(SideBySideEditorInput, name, description, secondaryInput, primaryInput);
}
}

View file

@ -249,23 +249,19 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant {
const editorOrModel = findEditor(textEditorModel, this.codeEditorService) || textEditorModel;
const mode = this.configurationService.getValue<'file' | 'modifications' | 'modificationsIfAvailable'>('editor.formatOnSaveMode', overrides);
// keeping things DRY :)
const formatWholeFile = async () => {
if (mode === 'file') {
await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token);
};
if (mode === 'modifications' || mode === 'modificationsIfAvailable') {
// try formatting modifications
const ranges = await this.instantiationService.invokeFunction(getModifiedRanges, isCodeEditor(editorOrModel) ? editorOrModel.getModel() : editorOrModel);
if (ranges) {
// version control reports changes
await this.instantiationService.invokeFunction(formatDocumentRangesWithSelectedProvider, editorOrModel, ranges, FormattingMode.Silent, nestedProgress, token);
} else if (ranges === null) {
// version control not found
await formatWholeFile();
}
} else {
await formatWholeFile();
const ranges = await this.instantiationService.invokeFunction(getModifiedRanges, isCodeEditor(editorOrModel) ? editorOrModel.getModel() : editorOrModel);
if (ranges === null && mode === 'modificationsIfAvailable') {
// no SCM, fallback to formatting the whole file iff wanted
await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token);
} else if (ranges) {
// formatted modified ranges
await this.instantiationService.invokeFunction(formatDocumentRangesWithSelectedProvider, editorOrModel, ranges, FormattingMode.Silent, nestedProgress, token);
}
}
}
}

View file

@ -575,10 +575,6 @@ class Launch extends AbstractLaunch implements ILaunch {
}
}
if (content === '') {
return { editor: null, created: false };
}
const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {

View file

@ -3,66 +3,67 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/repl';
import { URI as uri } from 'vs/base/common/uri';
import { IAction } from 'vs/base/common/actions';
import * as dom from 'vs/base/browser/dom';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ITextModel } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { memoize } from 'vs/base/common/decorators';
import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IDebugService, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration, REPL_VIEW_ID, CONTEXT_MULTI_SESSION_REPL, CONTEXT_DEBUG_STATE, getStateLabel } from 'vs/workbench/contrib/debug/common/debug';
import { HistoryNavigator } from 'vs/base/common/history';
import { IHistoryNavigationWidget } from 'vs/base/browser/history';
import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes';
import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { RunOnceScheduler } from 'vs/base/common/async';
import { FuzzyScore } from 'vs/base/common/filters';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider, ReplGroupRenderer } from 'vs/workbench/contrib/debug/browser/replViewer';
import { localize } from 'vs/nls';
import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { ReplFilter, ReplFilterState, ReplFilterActionViewItem } from 'vs/workbench/contrib/debug/browser/replFilter';
import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons';
import { registerAction2, MenuId, Action2, IMenuService, IMenu } from 'vs/platform/actions/common/actions';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IAction } from 'vs/base/common/actions';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { memoize } from 'vs/base/common/decorators';
import { FuzzyScore } from 'vs/base/common/filters';
import { HistoryNavigator } from 'vs/base/common/history';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { URI as uri } from 'vs/base/common/uri';
import 'vs/css!./media/repl';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ITextModel } from 'vs/editor/common/model';
import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, completionKindFromString, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { localize } from 'vs/nls';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget';
import { showHistoryKeybindingHint } from 'vs/platform/browser/historyWidgetKeybindingHint';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IViewPaneOptions, ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { ReplFilter, ReplFilterActionViewItem, ReplFilterState } from 'vs/workbench/contrib/debug/browser/replFilter';
import { ReplAccessibilityProvider, ReplDataSource, ReplDelegate, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplGroupRenderer, ReplRawObjectsRenderer, ReplSimpleElementsRenderer, ReplVariablesRenderer } from 'vs/workbench/contrib/debug/browser/replViewer';
import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, getStateLabel, IDebugConfiguration, IDebugService, IDebugSession, IReplElement, REPL_VIEW_ID, State } from 'vs/workbench/contrib/debug/common/debug';
import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
const $ = dom.$;
@ -493,7 +494,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
} else if (action.id === FILTER_ACTION_ID) {
const filterHistory = JSON.parse(this.storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[];
this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action,
localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState, filterHistory);
localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState, filterHistory, () => showHistoryKeybindingHint(this.keybindingService));
return this.filterActionViewItem;
}

View file

@ -138,6 +138,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
private placeholder: string,
private filters: ReplFilterState,
private history: string[],
private showHistoryHint: () => boolean,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IContextViewService private readonly contextViewService: IContextViewService) {
@ -188,7 +189,8 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
private createInput(container: HTMLElement): void {
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
placeholder: this.placeholder,
history: this.history
history: this.history,
showHistoryHint: this.showHistoryHint
}));
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
this.filterInputBox.value = this.filters.filterText;

View file

@ -224,8 +224,10 @@ export class ExtensionEditor extends EditorPane {
const subtitle = append(details, $('.subtitle'));
const publisher = append(append(subtitle, $('.subtitle-entry')), $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 }));
publisher.setAttribute('role', 'button');
const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 }));
const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 }));
rating.setAttribute('role', 'link'); // #132645
const description = append(details, $('.description'));

View file

@ -157,7 +157,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
} else {
const [first] = model.combined;
let text: string = '$(info)';
let text: string = '$(check-all)';
if (first.severity === Severity.Error) {
text = '$(error)';
} else if (first.severity === Severity.Warning) {
@ -279,7 +279,7 @@ class EditorStatusContribution implements IWorkbenchContribution {
private static _asStatusbarEntry(item: ILanguageStatus): IStatusbarEntry {
return {
name: item.name,
name: localize('name.pattern', '{0} (Language Status)', item.name),
text: item.label,
ariaLabel: item.accessibilityInfo?.label ?? item.label,
role: item.accessibilityInfo?.role,

View file

@ -29,6 +29,8 @@ import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { showHistoryKeybindingHint } from 'vs/platform/browser/historyWidgetKeybindingHint';
export interface IMarkersFiltersChangeEvent {
filterText?: boolean;
@ -245,6 +247,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem {
private focusContextKey: IContextKey<boolean>;
private readonly filtersAction: IAction;
private actionbar: ActionBar | null = null;
private keybindingService;
constructor(
action: IAction,
@ -252,9 +255,11 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IThemeService private readonly themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(null, action);
this.keybindingService = keybindingService;
this.focusContextKey = Constants.MarkerViewFilterFocusContextKey.bindTo(contextKeyService);
this.delayedFilterUpdate = new Delayer<void>(400);
this._register(toDisposable(() => this.delayedFilterUpdate.cancel()));
@ -319,7 +324,8 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem {
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER,
ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL,
history: this.markersView.filters.filterHistory
history: this.markersView.filters.filterHistory,
showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService)
}));
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
this.filterInputBox.value = this.markersView.filters.filterText;

View file

@ -160,6 +160,7 @@ class NotebookOutlineRenderer implements ITreeRenderer<OutlineEntry, FuzzyScore,
renderElement(node: ITreeNode<OutlineEntry, FuzzyScore>, _index: number, template: NotebookOutlineTemplate, _height: number | undefined): void {
const options: IIconLabelValueOptions = {
matches: createMatches(node.filterData),
labelEscapeNewLines: true,
extraClasses: []
};

View file

@ -9,6 +9,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { CATEGORIES } from 'vs/workbench/common/actions';
import { getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export class TroubleshootController extends Disposable implements INotebookEditorContribution {
@ -41,7 +42,7 @@ export class TroubleshootController extends Disposable implements INotebookEdito
private _log(cell: ICellViewModel, e: any) {
if (this._logging) {
const oldHeight = this._notebookEditor.getViewHeight(cell);
const oldHeight = (this._notebookEditor as NotebookEditorWidget).getViewHeight(cell);
console.log(`cell#${cell.handle}`, e, `${oldHeight} -> ${cell.layoutInfo.totalHeight}`);
}
}

View file

@ -648,3 +648,28 @@ export function insertCellAtIndex(viewModel: NotebookViewModel, index: number, s
return viewModel.cellAt(index)!;
}
/**
*
* @param index
* @param length
* @param newIdx in an index scheme for the state of the tree after the current cell has been "removed"
* @param synchronous
* @param pushedToUndoStack
*/
export function moveCellToIdx(editor: IActiveNotebookEditor, index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean = true): boolean {
const viewCell = editor.cellAt(index) as CellViewModel | undefined;
if (!viewCell) {
return false;
}
editor.textModel.applyEdits([
{
editType: CellEditType.Move,
index,
length,
newIdx
}
], synchronous, { kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() }, () => ({ kind: SelectionStateType.Index, focus: { start: newIdx, end: newIdx + 1 }, selections: [{ start: newIdx, end: newIdx + 1 }] }), undefined);
return true;
}

View file

@ -566,23 +566,12 @@ export interface INotebookEditor {
*/
revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Range): Promise<void>;
/**
* Get the view height of a cell (from the list view)
*/
getViewHeight(cell: ICellViewModel): number;
/**
* @param startIndex Inclusive
* @param endIndex Exclusive
*/
getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined;
/**
* @param startIndex Inclusive
* @param endIndex Exclusive
*/
getCellsFromViewRange(startIndex: number, endIndex: number): ReadonlyArray<ICellViewModel>;
/**
* Set hidden areas on cell text models.
*/

View file

@ -9,10 +9,8 @@ import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotebookDiffEditorModel, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IFileService } from 'vs/platform/files/common/files';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { ILabelService } from 'vs/platform/label/common/label';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
class NotebookDiffEditorModel extends EditorModel implements INotebookDiffEditorModel {
@ -52,8 +50,6 @@ export class NotebookDiffEditorInput extends DiffEditorInput {
override readonly original: NotebookEditorInput,
override readonly modified: NotebookEditorInput,
public readonly viewType: string,
@IFileService fileService: IFileService,
@ILabelService labelService: ILabelService,
@IEditorService editorService: IEditorService
) {
super(
@ -62,8 +58,6 @@ export class NotebookDiffEditorInput extends DiffEditorInput {
original,
modified,
undefined,
labelService,
fileService,
editorService
);
}

View file

@ -179,11 +179,11 @@ export class ListViewInfoAccessor extends Disposable {
return [];
}
return this.list.viewModel.getCells(range);
return this.list.viewModel.getCellsInRange(range);
}
getCellsInRange(range?: ICellRange): ReadonlyArray<ICellViewModel> {
return this.list.viewModel?.getCells(range) ?? [];
return this.list.viewModel?.getCellsInRange(range) ?? [];
}
setCellEditorSelection(cell: ICellViewModel, range: Range): void {
@ -1229,9 +1229,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this._outputFocus.set(false);
this.updateEditorFocus();
if (!this._overlayContainer.contains(document.activeElement)) {
this._webviewFocused = false;
}
this._webviewFocused = false;
}));
this._localStore.add(this._webview.webview.onDidFocus(() => {
@ -1829,10 +1827,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
return this._listViewInfoAccessor.getCellRangeFromViewRange(startIndex, endIndex);
}
getCellsFromViewRange(startIndex: number, endIndex: number): ReadonlyArray<ICellViewModel> {
return this._listViewInfoAccessor.getCellsFromViewRange(startIndex, endIndex);
}
getCellsInRange(range?: ICellRange): ReadonlyArray<ICellViewModel> {
return this._listViewInfoAccessor.getCellsInRange(range);
}
@ -1899,7 +1893,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
const existingDecorations = this._decortionKeyToIds.get(key) || [];
const newDecorations = this.viewModel.getCells(range).map(cell => ({
const newDecorations = this.viewModel.getCellsInRange(range).map(cell => ({
handle: cell.handle,
options: { className: decorationRule.className, outputClassName: decorationRule.className, topClassName: decorationRule.topClassName }
}));
@ -2911,6 +2905,6 @@ registerThemingParticipant((theme, collector) => {
const iconForegroundColor = theme.getColor(iconForeground);
if (iconForegroundColor) {
collector.addRule(`.monaco-workbench .notebookOverlay .codicon-debug-continue { color: ${iconForegroundColor}; }`);
collector.addRule(`.monaco-workbench .notebookOverlay .codicon-debug-continue { color: ${iconForegroundColor} !important; }`);
}
});

View file

@ -327,7 +327,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
const cell = this.element(i);
if (cell.cellKind === CellKind.Code) {
if (this._viewModel!.hasCell(cell.handle)) {
if (this._viewModel!.hasCell(cell)) {
hiddenOutputs.push(...cell?.outputsViewModels);
} else {
deletedOutputs.push(...cell?.outputsViewModels);
@ -447,9 +447,9 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
}
const selectionsLeft = [];
this.getSelectedElements().map(el => el.handle).forEach(handle => {
if (this._viewModel!.hasCell(handle)) {
selectionsLeft.push(handle);
this.getSelectedElements().forEach(el => {
if (this._viewModel!.hasCell(el)) {
selectionsLeft.push(el.handle);
}
});

View file

@ -11,10 +11,9 @@ import { clamp } from 'vs/base/common/numbers';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EndOfLinePreference, IModelDecorationOptions, IModelDeltaDecoration, IReadonlyTextBuffer, TrackedRangeStickiness } from 'vs/editor/common/model';
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack';
import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
@ -31,7 +30,7 @@ import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewM
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellEditType, CellKind, ICell, INotebookSearchOptions, IOutputDto, ISelectionState, NotebookCellMetadata, NotebookCellsChangeType, NotebookCellTextModelSplice, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, ICell, INotebookSearchOptions, ISelectionState, NotebookCellsChangeType, NotebookCellTextModelSplice, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { cellIndexesToRanges, cellRangesToIndexes, ICellRange, reduceRanges } from 'vs/workbench/contrib/notebook/common/notebookRange';
export interface INotebookEditorViewState {
@ -524,7 +523,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return this._viewCells[index];
}
getCells(range?: ICellRange): ReadonlyArray<ICellViewModel> {
getCellsInRange(range?: ICellRange): ReadonlyArray<ICellViewModel> {
if (!range) {
return this._viewCells.slice(0);
}
@ -589,8 +588,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return index + 1;
}
hasCell(handle: number) {
return this._handleToViewCellMapping.has(handle);
hasCell(cell: ICellViewModel) {
return this._handleToViewCellMapping.has(cell.handle);
}
getVersionId() {
@ -774,142 +773,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
}
}
createCell(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IOutputDto[], synchronous: boolean, pushUndoStop: boolean = true, previouslyPrimary: number | null = null, previouslyFocused: ICellViewModel[] = []): CellViewModel {
const beforeSelections = previouslyFocused.map(e => e.handle);
const endSelections: ISelectionState = { kind: SelectionStateType.Index, focus: { start: index, end: index + 1 }, selections: [{ start: index, end: index + 1 }] };
this._notebook.applyEdits([
{
editType: CellEditType.Replace,
index,
count: 0,
cells: [
{
cellKind: type,
language: language,
mime: undefined,
outputs: outputs,
metadata: metadata,
source: source
}
]
}
], synchronous, { kind: SelectionStateType.Handle, primary: previouslyPrimary, selections: beforeSelections }, () => endSelections, undefined);
return this._viewCells[index];
}
deleteCell(index: number, synchronous: boolean, pushUndoStop: boolean = true) {
const focusSelectionIndex = this.getFocus()?.start ?? null;
let endPrimarySelection: number | null = null;
if (index === focusSelectionIndex) {
if (focusSelectionIndex < this.length - 1) {
endPrimarySelection = this._viewCells[focusSelectionIndex + 1].handle;
} else if (focusSelectionIndex === this.length - 1 && this.length > 1) {
endPrimarySelection = this._viewCells[focusSelectionIndex - 1].handle;
}
}
let endSelections: number[] = this.selectionHandles.filter(handle => handle !== endPrimarySelection && handle !== this._viewCells[index]?.handle);
this._notebook.applyEdits([
{
editType: CellEditType.Replace,
index: index,
count: 1,
cells: []
}],
synchronous,
{ kind: SelectionStateType.Index, focus: this.getFocus(), selections: this.getSelections() },
() => ({ kind: SelectionStateType.Handle, primary: endPrimarySelection, selections: endSelections }),
undefined,
pushUndoStop
);
}
/**
*
* @param index
* @param length
* @param newIdx in an index scheme for the state of the tree after the current cell has been "removed"
* @param synchronous
* @param pushedToUndoStack
*/
moveCellToIdx(index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean = true): boolean {
const viewCell = this.viewCells[index] as CellViewModel;
if (!viewCell) {
return false;
}
this._notebook.applyEdits([
{
editType: CellEditType.Move,
index,
length,
newIdx
}
], synchronous, { kind: SelectionStateType.Index, focus: this.getFocus(), selections: this.getSelections() }, () => ({ kind: SelectionStateType.Index, focus: { start: newIdx, end: newIdx + 1 }, selections: [{ start: newIdx, end: newIdx + 1 }] }), undefined);
return true;
}
private _pushIfAbsent(positions: IPosition[], p: IPosition) {
const last = positions.length > 0 ? positions[positions.length - 1] : undefined;
if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) {
positions.push(p);
}
}
/**
* Add split point at the beginning and the end;
* Move end of line split points to the beginning of the next line;
* Avoid duplicate split points
*/
private _splitPointsToBoundaries(splitPoints: IPosition[], textBuffer: IReadonlyTextBuffer): IPosition[] | null {
const boundaries: IPosition[] = [];
const lineCnt = textBuffer.getLineCount();
const getLineLen = (lineNumber: number) => {
return textBuffer.getLineLength(lineNumber);
};
// split points need to be sorted
splitPoints = splitPoints.sort((l, r) => {
const lineDiff = l.lineNumber - r.lineNumber;
const columnDiff = l.column - r.column;
return lineDiff !== 0 ? lineDiff : columnDiff;
});
for (let sp of splitPoints) {
if (getLineLen(sp.lineNumber) + 1 === sp.column && sp.column !== 1 /** empty line */ && sp.lineNumber < lineCnt) {
sp = new Position(sp.lineNumber + 1, 1);
}
this._pushIfAbsent(boundaries, sp);
}
if (boundaries.length === 0) {
return null;
}
// boundaries already sorted and not empty
const modelStart = new Position(1, 1);
const modelEnd = new Position(lineCnt, getLineLen(lineCnt) + 1);
return [modelStart, ...boundaries, modelEnd];
}
computeCellLinesContents(cell: ICellViewModel, splitPoints: IPosition[]): string[] | null {
const rangeBoundaries = this._splitPointsToBoundaries(splitPoints, cell.textBuffer);
if (!rangeBoundaries) {
return null;
}
const newLineModels: string[] = [];
for (let i = 1; i < rangeBoundaries.length; i++) {
const start = rangeBoundaries[i - 1];
const end = rangeBoundaries[i];
newLineModels.push(cell.textBuffer.getValueInRange(new Range(start.lineNumber, start.column, end.lineNumber, end.column), EndOfLinePreference.TextDefined));
}
return newLineModels;
}
getEditorViewState(): INotebookEditorViewState {
const editingCells: { [key: number]: boolean; } = {};
this._viewCells.forEach((cell, i) => {

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel';
import { changeCellToKind, computeCellLinesContents, copyCellRange, joinNotebookCells, moveCellRange, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { changeCellToKind, computeCellLinesContents, copyCellRange, joinNotebookCells, moveCellRange, moveCellToIdx, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
import { Range } from 'vs/editor/common/core/range';
@ -13,6 +13,56 @@ import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
suite('CellOperations', () => {
test('move cells down', async function () {
await withTestNotebook(
[
['//a', 'javascript', CellKind.Code, [], {}],
['//b', 'javascript', CellKind.Code, [], {}],
['//c', 'javascript', CellKind.Code, [], {}],
],
(editor, viewModel) => {
moveCellToIdx(editor, 0, 1, 0, true);
// no-op
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//a');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//b');
moveCellToIdx(editor, 0, 1, 1, true);
// b, a, c
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//b');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//a');
assert.strictEqual(viewModel.cellAt(2)?.getText(), '//c');
moveCellToIdx(editor, 0, 1, 2, true);
// a, c, b
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//a');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//c');
assert.strictEqual(viewModel.cellAt(2)?.getText(), '//b');
}
);
});
test('move cells up', async function () {
await withTestNotebook(
[
['//a', 'javascript', CellKind.Code, [], {}],
['//b', 'javascript', CellKind.Code, [], {}],
['//c', 'javascript', CellKind.Code, [], {}],
],
(editor, viewModel) => {
moveCellToIdx(editor, 1, 1, 0, true);
// b, a, c
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//b');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//a');
moveCellToIdx(editor, 2, 1, 0, true);
// c, b, a
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//c');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//b');
assert.strictEqual(viewModel.cellAt(2)?.getText(), '//a');
}
);
});
test('Move cells - single cell', async function () {
await withTestNotebook(
[

View file

@ -52,8 +52,8 @@ suite('ListViewInfoAccessor', () => {
assert.deepStrictEqual(listViewInfoAccessor.getCellRangeFromViewRange(0, 1), { start: 0, end: 2 });
assert.deepStrictEqual(listViewInfoAccessor.getCellRangeFromViewRange(1, 2), { start: 2, end: 5 });
assert.deepStrictEqual(listViewInfoAccessor.getCellsFromViewRange(0, 1), viewModel.getCells({ start: 0, end: 2 }));
assert.deepStrictEqual(listViewInfoAccessor.getCellsFromViewRange(1, 2), viewModel.getCells({ start: 2, end: 5 }));
assert.deepStrictEqual(listViewInfoAccessor.getCellsFromViewRange(0, 1), viewModel.getCellsInRange({ start: 0, end: 2 }));
assert.deepStrictEqual(listViewInfoAccessor.getCellsFromViewRange(1, 2), viewModel.getCellsInRange({ start: 2, end: 5 }));
const notebookEditor = new class extends mock<INotebookEditor>() {
override getViewIndexByModelIndex(index: number) { return listViewInfoAccessor.getViewIndex(viewModel.viewCells[index]!); }

View file

@ -6,6 +6,7 @@
import * as assert from 'assert';
import { IModeService } from 'vs/editor/common/services/modeService';
import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel';
import { runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { NotebookCellSelectionCollection } from 'vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection';
import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { createNotebookCellList, setupInstantiationService, TestCell, withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
@ -288,9 +289,10 @@ suite('NotebookCellList focus/selection', () => {
],
(editor, viewModel) => {
viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }] });
viewModel.deleteCell(1, true, false);
runDeleteAction(editor, viewModel.cellAt(1)!);
// viewModel.deleteCell(1, true, false);
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 });
assert.deepStrictEqual(viewModel.getSelections(), []);
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]);
});
});

View file

@ -15,7 +15,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { insertCellAtIndex } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { insertCellAtIndex, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { reduceCellRanges } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
@ -57,7 +57,7 @@ suite('NotebookViewModel', () => {
assert.strictEqual(viewModel.notebookDocument.cells.length, 3);
assert.strictEqual(viewModel.getCellIndex(cell), 1);
viewModel.deleteCell(1, true);
runDeleteAction(editor, viewModel.cellAt(1)!);
assert.strictEqual(viewModel.length, 2);
assert.strictEqual(viewModel.notebookDocument.cells.length, 2);
assert.strictEqual(viewModel.getCellIndex(cell), -1);
@ -65,56 +65,6 @@ suite('NotebookViewModel', () => {
);
});
test('move cells down', async function () {
await withTestNotebook(
[
['//a', 'javascript', CellKind.Code, [], {}],
['//b', 'javascript', CellKind.Code, [], {}],
['//c', 'javascript', CellKind.Code, [], {}],
],
(editor, viewModel) => {
viewModel.moveCellToIdx(0, 1, 0, true);
// no-op
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//a');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//b');
viewModel.moveCellToIdx(0, 1, 1, true);
// b, a, c
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//b');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//a');
assert.strictEqual(viewModel.cellAt(2)?.getText(), '//c');
viewModel.moveCellToIdx(0, 1, 2, true);
// a, c, b
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//a');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//c');
assert.strictEqual(viewModel.cellAt(2)?.getText(), '//b');
}
);
});
test('move cells up', async function () {
await withTestNotebook(
[
['//a', 'javascript', CellKind.Code, [], {}],
['//b', 'javascript', CellKind.Code, [], {}],
['//c', 'javascript', CellKind.Code, [], {}],
],
(editor, viewModel) => {
viewModel.moveCellToIdx(1, 1, 0, true);
// b, a, c
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//b');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//a');
viewModel.moveCellToIdx(2, 1, 0, true);
// c, b, a
assert.strictEqual(viewModel.cellAt(0)?.getText(), '//c');
assert.strictEqual(viewModel.cellAt(1)?.getText(), '//b');
assert.strictEqual(viewModel.cellAt(2)?.getText(), '//a');
}
);
});
test('index', async function () {
await withTestNotebook(
[
@ -129,7 +79,7 @@ suite('NotebookViewModel', () => {
const cell = insertCellAtIndex(viewModel, insertIndex, 'var c = 3;', 'javascript', CellKind.Code, {}, [], true);
const addedCellIndex = viewModel.getCellIndex(cell);
viewModel.deleteCell(addedCellIndex, true);
runDeleteAction(editor, viewModel.cellAt(addedCellIndex)!);
const secondInsertIndex = viewModel.getCellIndex(lastViewCell) + 1;
const cell2 = insertCellAtIndex(viewModel, secondInsertIndex, 'var d = 4;', 'javascript', CellKind.Code, {}, [], true);
@ -192,7 +142,7 @@ suite('NotebookViewModel Decorations', () => {
end: 3
});
viewModel.deleteCell(0, true);
runDeleteAction(editor, viewModel.cellAt(0)!);
assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), {
start: 1,
@ -206,14 +156,14 @@ suite('NotebookViewModel Decorations', () => {
end: 3
});
viewModel.deleteCell(3, true);
runDeleteAction(editor, viewModel.cellAt(3)!);
assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), {
start: 1,
end: 2
});
viewModel.deleteCell(1, true);
runDeleteAction(editor, viewModel.cellAt(1)!);
assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), {
start: 0,
@ -370,18 +320,18 @@ suite('NotebookViewModel API', () => {
['# header b', 'markdown', CellKind.Markup, [], {}]
],
(editor, viewModel) => {
assert.strictEqual(viewModel.getCells().length, 3);
assert.deepStrictEqual(viewModel.getCells({ start: 0, end: 1 }).map(cell => cell.getText()), ['# header a']);
assert.deepStrictEqual(viewModel.getCells({ start: 0, end: 2 }).map(cell => cell.getText()), ['# header a', 'var b = 1;']);
assert.deepStrictEqual(viewModel.getCells({ start: 0, end: 3 }).map(cell => cell.getText()), ['# header a', 'var b = 1;', '# header b']);
assert.deepStrictEqual(viewModel.getCells({ start: 0, end: 4 }).map(cell => cell.getText()), ['# header a', 'var b = 1;', '# header b']);
assert.deepStrictEqual(viewModel.getCells({ start: 1, end: 4 }).map(cell => cell.getText()), ['var b = 1;', '# header b']);
assert.deepStrictEqual(viewModel.getCells({ start: 2, end: 4 }).map(cell => cell.getText()), ['# header b']);
assert.deepStrictEqual(viewModel.getCells({ start: 3, end: 4 }).map(cell => cell.getText()), []);
assert.strictEqual(viewModel.getCellsInRange().length, 3);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 0, end: 1 }).map(cell => cell.getText()), ['# header a']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 0, end: 2 }).map(cell => cell.getText()), ['# header a', 'var b = 1;']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 0, end: 3 }).map(cell => cell.getText()), ['# header a', 'var b = 1;', '# header b']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 0, end: 4 }).map(cell => cell.getText()), ['# header a', 'var b = 1;', '# header b']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 1, end: 4 }).map(cell => cell.getText()), ['var b = 1;', '# header b']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 2, end: 4 }).map(cell => cell.getText()), ['# header b']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 3, end: 4 }).map(cell => cell.getText()), []);
// no one should use an invalid range but `getCells` should be able to handle that.
assert.deepStrictEqual(viewModel.getCells({ start: -1, end: 1 }).map(cell => cell.getText()), ['# header a']);
assert.deepStrictEqual(viewModel.getCells({ start: 3, end: 0 }).map(cell => cell.getText()), ['# header a', 'var b = 1;', '# header b']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: -1, end: 1 }).map(cell => cell.getText()), ['# header a']);
assert.deepStrictEqual(viewModel.getCellsInRange({ start: 3, end: 0 }).map(cell => cell.getText()), ['# header a', 'var b = 1;', '# header b']);
}
);
});

View file

@ -249,7 +249,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic
override cellAt(index: number) { return viewModel.cellAt(index)!; }
override getCellIndex(cell: ICellViewModel) { return viewModel.getCellIndex(cell); }
override getCellIndexByHandle(handle: number) { return viewModel.getCellIndexByHandle(handle); }
override getCellsInRange(range?: ICellRange) { return viewModel.getCells(range); }
override getCellsInRange(range?: ICellRange) { return viewModel.getCellsInRange(range); }
override getNextVisibleCellIndex(index: number) { return viewModel.getNextVisibleCellIndex(index); }
getControl() { return this; }
override get onDidChangeSelection() { return viewModel.onDidChangeSelection as Event<any>; }

View file

@ -54,12 +54,12 @@ export class KeybindingsSearchWidget extends SearchWidget {
constructor(parent: HTMLElement, options: KeybindingsSearchOptions,
@IContextViewService contextViewService: IContextViewService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(parent, options, contextViewService, instantiationService, themeService, contextKeyService);
super(parent, options, contextViewService, instantiationService, themeService, contextKeyService, keybindingService);
this._register(attachInputBoxStyler(this.inputBox, themeService));
this._register(toDisposable(() => this.stopRecordingKeys()));
this._firstPart = null;

View file

@ -25,7 +25,7 @@ import { DefineKeybindingWidget, KeybindingsSearchWidget } from 'vs/workbench/co
import { CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE } from 'vs/workbench/contrib/preferences/common/preferences';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list';
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeyCode } from 'vs/base/common/keyCodes';
@ -494,9 +494,10 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP
)) as WorkbenchTable<IKeybindingItemEntry>;
this._register(this.keybindingsTable.onContextMenu(e => this.onContextMenu(e)));
this._register(this.keybindingsTable.onDidChangeFocus(e => this.onFocusChange(e)));
this._register(this.keybindingsTable.onDidChangeFocus(e => this.onFocusChange()));
this._register(this.keybindingsTable.onDidFocus(() => {
this.keybindingsTable.getHTMLElement().classList.add('focused');
this.onFocusChange();
}));
this._register(this.keybindingsTable.onDidBlur(() => {
this.keybindingsTable.getHTMLElement().classList.remove('focused');
@ -685,9 +686,9 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP
}
}
private onFocusChange(e: IListEvent<IKeybindingItemEntry>): void {
private onFocusChange(): void {
this.keybindingFocusContextKey.reset();
const element = e.elements[0];
const element = this.keybindingsTable.getFocusedElements()[0];
if (!element) {
return;
}

View file

@ -22,10 +22,12 @@ import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/brows
import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { showHistoryKeybindingHint } from 'vs/platform/browser/historyWidgetKeybindingHint';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ILabelService } from 'vs/platform/label/common/label';
import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
@ -370,7 +372,8 @@ export class SearchWidget extends Widget {
@IContextViewService private readonly contextViewService: IContextViewService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IKeybindingService protected readonly keybindingService: IKeybindingService
) {
super();
this.create(parent);
@ -420,7 +423,8 @@ export class SearchWidget extends Widget {
}
protected createInputBox(parent: HTMLElement): HistoryInputBox {
const box = this._register(new ContextScopedHistoryInputBox(parent, this.contextViewService, this.options, this.contextKeyService));
const showHistoryHint = () => showHistoryKeybindingHint(this.keybindingService);
const box = this._register(new ContextScopedHistoryInputBox(parent, this.contextViewService, { ...this.options, showHistoryHint }, this.contextKeyService));
this._register(attachInputBoxStyler(box, this.themeService));
return box;

View file

@ -15,8 +15,10 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import type { IThemable } from 'vs/base/common/styler';
import * as nls from 'vs/nls';
import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { showHistoryKeybindingHint } from 'vs/platform/browser/historyWidgetKeybindingHint';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { attachCheckboxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@ -49,7 +51,8 @@ export class PatternInputWidget extends Widget implements IThemable {
constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null),
@IThemeService protected themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService protected readonly configurationService: IConfigurationService
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
) {
super();
options = {
@ -151,7 +154,8 @@ export class PatternInputWidget extends Widget implements IThemable {
validationOptions: {
validation: undefined
},
history: options.history || []
history: options.history || [],
showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService)
}, this.contextKeyService);
this._register(attachInputBoxStyler(this.inputBox, this.themeService));
this._register(this.inputBox.onDidChange(() => this._onSubmit.fire(true)));
@ -192,8 +196,9 @@ export class IncludePatternInputWidget extends PatternInputWidget {
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService);
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService, keybindingService);
}
private useSearchInEditorsBox!: Checkbox;
@ -243,8 +248,9 @@ export class ExcludePatternInputWidget extends PatternInputWidget {
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService);
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService, keybindingService);
}
private useExcludesAndIgnoreFilesBox!: Checkbox;

View file

@ -320,7 +320,7 @@ export class SearchView extends ViewPane {
this.inputPatternIncludes = this._register(this.instantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, {
ariaLabel: filesToIncludeTitle,
placeholder: nls.localize('placeholder.includes', "(e.g. *.ts, src/**/include)"),
placeholder: nls.localize('placeholder.includes', "e.g. *.ts, src/**/include"),
showPlaceholderOnFocus: true,
history: patternIncludesHistory,
}));
@ -339,7 +339,7 @@ export class SearchView extends ViewPane {
dom.append(excludesList, $('h4', undefined, excludesTitle));
this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, {
ariaLabel: excludesTitle,
placeholder: nls.localize('placeholder.excludes', "(e.g. *.ts, src/**/exclude)"),
placeholder: nls.localize('placeholder.excludes', "e.g. *.ts, src/**/exclude"),
showPlaceholderOnFocus: true,
history: patternExclusionsHistory,
}));

View file

@ -35,6 +35,7 @@ import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { IViewsService } from 'vs/workbench/common/views';
import { searchReplaceAllIcon, searchHideReplaceIcon, searchShowContextIcon, searchShowReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
import { ToggleSearchEditorContextLinesCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants';
import { showHistoryKeybindingHint } from 'vs/platform/browser/historyWidgetKeybindingHint';
/** Specified in searchview.css */
export const SingleLineInputHeight = 24;
@ -156,7 +157,7 @@ export class SearchWidget extends Widget {
@IContextViewService private readonly contextViewService: IContextViewService,
@IThemeService private readonly themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IKeybindingService private readonly keyBindingService: IKeybindingService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IClipboardService private readonly clipboardServce: IClipboardService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
@ -235,6 +236,7 @@ export class SearchWidget extends Widget {
clearHistory(): void {
this.searchInput.inputBox.clearHistory();
this.replaceInput.inputBox.clearHistory();
}
showNextSearchTerm() {
@ -306,10 +308,11 @@ export class SearchWidget extends Widget {
label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search'),
validation: (value: string) => this.validateSearchInput(value),
placeholder: nls.localize('search.placeHolder', "Search"),
appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId), this.keyBindingService),
appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keyBindingService),
appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleRegexCommandId), this.keyBindingService),
appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId), this.keybindingService),
appendWholeWordsLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keybindingService),
appendRegexLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.ToggleRegexCommandId), this.keybindingService),
history: options.searchHistory,
showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService),
flexibleHeight: true,
flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT
};
@ -354,7 +357,7 @@ export class SearchWidget extends Widget {
this.showContextCheckbox = new Checkbox({
isChecked: false,
title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keyBindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId), this.keyBindingService),
title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keybindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId), this.keybindingService),
icon: searchShowContextIcon
});
this._register(this.showContextCheckbox.onChange(() => this.onContextLinesChanged()));
@ -396,8 +399,9 @@ export class SearchWidget extends Widget {
this.replaceInput = this._register(new ContextScopedReplaceInput(replaceBox, this.contextViewService, {
label: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview'),
placeholder: nls.localize('search.replace.placeHolder', "Replace"),
appendPreserveCaseLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.TogglePreserveCaseId), this.keyBindingService),
appendPreserveCaseLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.TogglePreserveCaseId), this.keybindingService),
history: options.replaceHistory,
showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService),
flexibleHeight: true,
flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT
}, this.contextKeyService, true));
@ -452,7 +456,7 @@ export class SearchWidget extends Widget {
setReplaceAllActionState(enabled: boolean): void {
if (this.replaceAllAction.enabled !== enabled) {
this.replaceAllAction.enabled = enabled;
this.replaceAllAction.label = enabled ? SearchWidget.REPLACE_ALL_ENABLED_LABEL(this.keyBindingService) : SearchWidget.REPLACE_ALL_DISABLED_LABEL;
this.replaceAllAction.label = enabled ? SearchWidget.REPLACE_ALL_ENABLED_LABEL(this.keybindingService) : SearchWidget.REPLACE_ALL_DISABLED_LABEL;
this.updateReplaceActiveState();
}
}

View file

@ -572,6 +572,8 @@ export interface ITerminalInstance {
readonly navigationMode: INavigationMode | undefined;
description: string | undefined;
userHome: string | undefined
/**
* Shows the environment information hover if the widget exists.
*/

View file

@ -543,7 +543,7 @@ export function registerTerminalActions() {
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow },
when: TerminalContextKeys.focus,
when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()),
weight: KeybindingWeight.WorkbenchContrib
},
precondition: TerminalContextKeys.processSupported
@ -583,7 +583,7 @@ export function registerTerminalActions() {
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.End,
linux: { primary: KeyMod.Shift | KeyCode.End },
when: TerminalContextKeys.focus,
when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()),
weight: KeybindingWeight.WorkbenchContrib
},
precondition: TerminalContextKeys.processSupported
@ -603,7 +603,7 @@ export function registerTerminalActions() {
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow },
when: TerminalContextKeys.focus,
when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()),
weight: KeybindingWeight.WorkbenchContrib
},
precondition: TerminalContextKeys.processSupported
@ -643,7 +643,7 @@ export function registerTerminalActions() {
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.Home,
linux: { primary: KeyMod.Shift | KeyCode.Home },
when: TerminalContextKeys.focus,
when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()),
weight: KeybindingWeight.WorkbenchContrib
},
precondition: TerminalContextKeys.processSupported

View file

@ -22,7 +22,7 @@ import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal';
import { isLinux, isWindows } from 'vs/base/common/platform';
const MINIMUM_FONT_SIZE = 6;
const MAXIMUM_FONT_SIZE = 25;
const MAXIMUM_FONT_SIZE = 100;
/**
* Encapsulates terminal configuration logic, the primary purpose of this file is so that platform

View file

@ -70,6 +70,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
import { isSafari } from 'vs/base/browser/browser';
import { ISeparator, template } from 'vs/base/common/labels';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
// How long in milliseconds should an average frame take to render for a notification to appear
// which suggests the fallback DOM-based renderer
@ -175,6 +176,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _staticTitle?: string;
private _workspaceFolder?: string;
private _labelComputer?: TerminalLabelComputer;
private _userHome?: string;
target?: TerminalLocation;
get instanceId(): number { return this._instanceId; }
@ -231,6 +233,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
get initialCwd(): string | undefined { return this._initialCwd; }
get capabilities(): ProcessCapability[] { return this._capabilities; }
get description(): string | undefined { return this._description || this.shellLaunchConfig.description; }
get userHome(): string | undefined { return this._userHome; }
// The onExit event is special in that it fires and is disposed after the terminal instance
// itself is disposed
private readonly _onExit = new Emitter<number | undefined>();
@ -278,6 +281,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
resource: URI | undefined,
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
@IPathService private readonly _pathService: IPathService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@INotificationService private readonly _notificationService: INotificationService,
@ -610,6 +614,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
letterSpacing: font.letterSpacing,
lineHeight: font.lineHeight,
minimumContrastRatio: config.minimumContrastRatio,
cursorBlink: config.cursorBlinking,
cursorStyle: config.cursorStyle,
cursorWidth: config.cursorWidth,
bellStyle: 'none',
macOptionIsMeta: config.macOptionIsMeta,
macOptionClickForcesSelection: config.macOptionClickForcesSelection,
@ -690,7 +697,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._xtermTypeAhead = this._register(this._instantiationService.createInstance(TypeAheadAddon, this._processManager, this._configHelper));
this._xterm.loadAddon(this._xtermTypeAhead);
this._userHome = (await this._pathService.userHome()).fsPath;
return xterm;
}
@ -1745,7 +1752,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this._isVisible) {
// HACK: Force the renderer to unpause by simulating an IntersectionObserver event.
// This is to fix an issue where dragging the window to the top of the screen to
// This is to fix an issue where dragging the windpow to the top of the screen to
// maximize on Windows/Linux would fire an event saying that the terminal was not
// visible.
if (this._xterm.getOption('rendererType') === 'canvas') {
@ -2281,7 +2288,7 @@ export class TerminalLabelComputer extends Disposable {
readonly onDidChangeLabel = this._onDidChangeLabel.event;
constructor(
private readonly _configHelper: TerminalConfigHelper,
private readonly _instance: Pick<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>,
private readonly _instance: Pick<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'userHome' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
) {
super();
@ -2314,14 +2321,11 @@ export class TerminalLabelComputer extends Disposable {
if (this._instance.staticTitle && labelType === TerminalLabelType.Title) {
return this._instance.staticTitle.replace(/[\n\r\t]/g, '') || templateProperties.process?.replace(/[\n\r\t]/g, '') || '';
}
const detection = this._instance.capabilities.includes(ProcessCapability.CwdDetection);
const zeroRootWorkspace = this._workspaceContextService.getWorkspace().folders.length === 0 && templateProperties.cwd === (this._instance.userHome || this._configHelper.config.cwd);
const singleRootWorkspace = this._workspaceContextService.getWorkspace().folders.length === 1 && templateProperties.cwd === (this._configHelper.config.cwd || this._workspaceContextService.getWorkspace().folders[0]?.uri.fsPath);
templateProperties.cwdFolder = (!templateProperties.cwd || !detection || zeroRootWorkspace || singleRootWorkspace) ? '' : path.basename(templateProperties.cwd);
if (!templateProperties.cwd || !this._instance.capabilities.includes(ProcessCapability.CwdDetection) ||
(this._workspaceContextService.getWorkspace().folders.length <= 1 &&
(templateProperties.cwd === (this._configHelper.config.cwd || (this._workspaceContextService.getWorkspace().folders[0].uri.fsPath))))) {
templateProperties.cwdFolder = '';
} else if (templateProperties.cwd && typeof templateProperties.cwd === 'string') {
templateProperties.cwdFolder = path.basename(templateProperties.cwd);
}
//Remove special characters that could mess with rendering
const label = template(labelTemplate, (templateProperties as unknown) as { [key: string]: string | ISeparator | undefined | null; }).replace(/[\n\r\t]/g, '');
return label === '' && labelType === TerminalLabelType.Title ? (this._instance.processName || '') : label;

View file

@ -207,7 +207,7 @@ export class TerminalTabbedView extends Disposable {
const style = window.getComputedStyle(this._tabListElement);
ctx.font = `${style.fontStyle} ${style.fontSize} ${style.fontFamily}`;
const maxInstanceWidth = this._terminalGroupService.instances.reduce((p, c) => {
return Math.max(p, ctx.measureText(c.title + (c.shellLaunchConfig.description || '')).width + this._getAdditionalWidth(c));
return Math.max(p, ctx.measureText(c.title + (c.description || '')).width + this._getAdditionalWidth(c));
}, 0);
idealWidth = Math.ceil(Math.max(maxInstanceWidth, TerminalTabsListSizes.WideViewMinimumWidth));
}
@ -222,7 +222,7 @@ export class TerminalTabbedView extends Disposable {
private _getAdditionalWidth(instance: ITerminalInstance): number {
// Size to include padding, icon, status icon (if any), split annotation (if any), + a little more
const additionalWidth = 30;
const additionalWidth = 40;
const statusIconWidth = instance.statusList.statuses.length > 0 ? STATUS_ICON_WIDTH : 0;
const splitAnnotationWidth = (this._terminalGroupService.getGroupForInstance(instance)?.terminalInstances.length || 0) > 1 ? SPLIT_ANNOTATION_WIDTH : 0;
return additionalWidth + splitAnnotationWidth + statusIconWidth;

View file

@ -154,7 +154,7 @@ export interface ITerminalConfiguration {
gpuAcceleration: 'auto' | 'on' | 'canvas' | 'off';
rightClickBehavior: 'default' | 'copyPaste' | 'paste' | 'selectWord';
cursorBlinking: boolean;
cursorStyle: string;
cursorStyle: 'block' | 'underline' | 'bar';
cursorWidth: number;
drawBoldTextInBrightColors: boolean;
fastScrollSensitivity: number;
@ -188,7 +188,6 @@ export interface ITerminalConfiguration {
splitCwd: 'workspaceRoot' | 'initial' | 'inherited';
windowsEnableConpty: boolean;
wordSeparators: string;
titleMode: 'executable' | 'sequence';
enableFileLinks: boolean;
unicodeVersion: '6' | '11';
experimentalLinkProvider: boolean;

View file

@ -151,7 +151,9 @@ const terminalConfiguration: IConfigurationNode = {
[TerminalSettingId.FontSize]: {
description: localize('terminal.integrated.fontSize', "Controls the font size in pixels of the terminal."),
type: 'number',
default: isMacintosh ? 12 : 14
default: isMacintosh ? 12 : 14,
minimum: 6,
maximum: 100
},
[TerminalSettingId.LetterSpacing]: {
description: localize('terminal.integrated.letterSpacing', "Controls the letter spacing of the terminal, this is an integer value which represents the amount of additional pixels to add between characters."),
@ -417,16 +419,6 @@ const terminalConfiguration: IConfigurationNode = {
type: 'string',
default: ' ()[]{}\',"`─'
},
[TerminalSettingId.TitleMode]: {
description: localize('terminal.integrated.titleMode', "Determines how the terminal's title is set, this shows up in the terminal's tab or dropdown entry."),
type: 'string',
enum: ['executable', 'sequence'],
markdownEnumDescriptions: [
localize('titleMode.executable', "The title is set by the terminal, the name of the detected foreground process will be used."),
localize('titleMode.sequence', "The title is set by the process via an escape sequence, this is useful if your shell dynamically sets the title.")
],
default: 'executable'
},
[TerminalSettingId.EnableFileLinks]: {
description: localize('terminal.integrated.enableFileLinks', "Whether to enable file links in the terminal. Links can be slow when working on a network drive in particular because each file link is verified against the file system. Changing this will take effect only in new terminals."),
type: 'boolean',

View file

@ -106,7 +106,7 @@ suite('Workbench - TerminalConfigHelper', () => {
});
configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!);
configHelper.panelContainer = fixture;
assert.strictEqual(configHelper.getFont().fontSize, 25, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it');
assert.strictEqual(configHelper.getFont().fontSize, 100, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it');
await configurationService.setUserConfiguration('editor', {
fontFamily: 'foo'

View file

@ -17,7 +17,7 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { basename } from 'path';
function createInstance(partial?: Partial<ITerminalInstance>): Pick<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'> {
function createInstance(partial?: Partial<ITerminalInstance>): Pick<ITerminalInstance, 'shellLaunchConfig' | 'userHome' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'> {
return {
shellLaunchConfig: {},
cwd: 'cwd',
@ -29,6 +29,7 @@ function createInstance(partial?: Partial<ITerminalInstance>): Pick<ITerminalIns
capabilities: isWindows ? [] : [ProcessCapability.CwdDetection],
title: '',
description: '',
userHome: undefined,
...partial
};
}
@ -183,6 +184,7 @@ suite('Workbench - TerminalInstance', () => {
strictEqual(terminalLabelComputer.description, 'root2');
}
});
//TODO: enable and test userHome
test.skip('should hide cwdFolder in empty workspaces when cwd matches the workspace\'s default cwd ($HOME or $HOMEDRIVE$HOMEPATH)', async () => {
configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } }, cwd: ROOT_1 } });
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!);

View file

@ -89,7 +89,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
value: this.state.text.value,
placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"),
},
history: this.history.get([]),
history: this.history.get([])
}));
this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService));

View file

@ -465,6 +465,10 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
return false;
}
if (!this.configurationService.getValue('diffEditor.renderSideBySide')) {
return isEqual(userDataSyncResource.merged, model.uri);
}
return true;
}

View file

@ -142,7 +142,6 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Fire event to outside parties
this._onDidActiveEditorChange.fire();
this._onDidEditorsChange.fire([{ groupId: activeGroup.id, editor: this.lastActiveEditor, kind: GroupChangeKind.EDITOR_ACTIVE }]);
}
private registerGroupListeners(group: IEditorGroupView): void {
@ -151,6 +150,9 @@ export class EditorService extends Disposable implements EditorServiceImpl {
groupDisposables.add(group.onDidGroupChange(e => {
switch (e.kind) {
case GroupChangeKind.EDITOR_ACTIVE:
if (group.activeEditor) {
this._onDidEditorsChange.fire([{ groupId: group.id, editor: group.activeEditor, kind: GroupChangeKind.EDITOR_ACTIVE }]);
}
this.handleActiveEditorChange(group);
this._onDidVisibleEditorsChange.fire();
break;

View file

@ -463,7 +463,7 @@ class CompositeMouseTracker extends Widget {
private _isMouseIn: boolean = false;
private _mouseTimeout: number | undefined;
private readonly _onMouseOut = new Emitter<void>();
private readonly _onMouseOut = this._register(new Emitter<void>());
get onMouseOut(): Event<void> { return this._onMouseOut.event; }
constructor(

View file

@ -103,7 +103,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
private registerListeners(): void {
// Creates
this._register(this.files.onDidCreate(model => {
this._register(this.files.onDidResolve(({ model }) => {
if (model.isReadonly() || model.hasState(TextFileEditorModelState.ORPHAN)) {
this._onDidChange.fire([model.resource]);
}

View file

@ -947,9 +947,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
case TextFileEditorModelState.ERROR:
return this.inErrorMode;
case TextFileEditorModelState.ORPHAN:
if (this.resource.fsPath === '/Users/bpasero/Desktop/tab-labels/deleted.txt' || this.resource.fsPath === '/Users/bpasero/Desktop/tab-labels/deleted-readonly.txt') {
return true;
}
return this.inOrphanMode;
case TextFileEditorModelState.PENDING_SAVE:
return this.saveSequentializer.hasPending();
@ -1042,10 +1039,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
override isReadonly(): boolean {
if (this.resource.fsPath === '/Users/bpasero/Desktop/tab-labels/readonly.txt' || this.resource.fsPath === '/Users/bpasero/Desktop/tab-labels/deleted-readonly.txt') {
return true;
}
return this.lastResolvedFileStat?.readonly || this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly);
}

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