Merge branch 'main' into aiday/indentationTestsAccrossDifferentLanguages

This commit is contained in:
Aiday Marlen Kyzy 2024-03-22 21:50:30 +01:00
commit 4e98d4b801
No known key found for this signature in database
GPG key ID: 24A8B53DBD26FF4E
32 changed files with 603 additions and 186 deletions

View file

@ -57,6 +57,7 @@ export class BreadcrumbsWidget {
private _focusedItemIdx: number = -1;
private _selectedItemIdx: number = -1;
private _pendingDimLayout: IDisposable | undefined;
private _pendingLayout: IDisposable | undefined;
private _dimension: dom.Dimension | undefined;
@ -100,6 +101,7 @@ export class BreadcrumbsWidget {
dispose(): void {
this._disposables.dispose();
this._pendingLayout?.dispose();
this._pendingDimLayout?.dispose();
this._onDidSelectItem.dispose();
this._onDidFocusItem.dispose();
this._onDidChangeFocus.dispose();
@ -112,11 +114,12 @@ export class BreadcrumbsWidget {
if (dim && dom.Dimension.equals(dim, this._dimension)) {
return;
}
this._pendingLayout?.dispose();
if (dim) {
// only measure
this._pendingLayout = this._updateDimensions(dim);
this._pendingDimLayout?.dispose();
this._pendingDimLayout = this._updateDimensions(dim);
} else {
this._pendingLayout?.dispose();
this._pendingLayout = this._updateScrollbar();
}
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, isAncestor, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { EventType as TouchEventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
@ -1517,6 +1517,9 @@ export class ListView<T> implements IListView<T> {
if (item.row) {
item.row.domNode.style.height = '';
item.size = item.row.domNode.offsetHeight;
if (item.size === 0 && !isAncestor(item.row.domNode, getWindow(item.row.domNode).document.body)) {
console.warn('Measuring item node that is not in DOM! Add ListView to the DOM before measuring row height!');
}
item.lastDynamicHeightWidth = this.renderWidth;
return item.size - size;
}

View file

@ -305,7 +305,7 @@ suite('Change Indentation to Tabs - TypeScript/Javascript', () => {
});
});
suite('`Full` Auto Indent On Paste - TypeScript/JavaScript', () => {
suite('Auto Indent On Paste - TypeScript/JavaScript', () => {
const languageId = 'ts-test';
let disposables: DisposableStore;
@ -398,6 +398,84 @@ suite('`Full` Auto Indent On Paste - TypeScript/JavaScript', () => {
});
});
test('issue #29803: do not indent when pasting text with only one line', () => {
// https://github.com/microsoft/vscode/issues/29803
const model = createTextModel([
'const linkHandler = new Class(a, b, c,',
' d)'
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(2, 6, 2, 6));
const text = ', null';
viewModel.paste(text, true, undefined, 'keyboard');
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
autoIndentOnPasteController.trigger(new Range(2, 6, 2, 11));
assert.strictEqual(model.getValue(), [
'const linkHandler = new Class(a, b, c,',
' d, null)'
].join('\n'));
});
});
test('issue #29753: incorrect indentation after comment', () => {
// https://github.com/microsoft/vscode/issues/29753
const model = createTextModel([
'class A {',
' /**',
' * used only for debug purposes.',
' */',
' private _codeInfo: KeyMapping[];',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(5, 24, 5, 34));
const text = 'IMacLinuxKeyMapping';
viewModel.paste(text, true, undefined, 'keyboard');
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
autoIndentOnPasteController.trigger(new Range(5, 24, 5, 43));
assert.strictEqual(model.getValue(), [
'class A {',
' /**',
' * used only for debug purposes.',
' */',
' private _codeInfo: IMacLinuxKeyMapping[];',
'}',
].join('\n'));
});
});
test('issue #29753: incorrect indentation of header comment', () => {
// https://github.com/microsoft/vscode/issues/29753
const model = createTextModel('', languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
const text = [
'/*----------------',
' * Copyright (c) ',
' * Licensed under ...',
' *-----------------*/',
].join('\n');
viewModel.paste(text, true, undefined, 'keyboard');
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
autoIndentOnPasteController.trigger(new Range(1, 1, 4, 22));
assert.strictEqual(model.getValue(), text);
});
});
// Failing tests found in issues...
test.skip('issue #181065: Incorrect paste of object within comment', () => {
@ -594,7 +672,7 @@ suite('`Full` Auto Indent On Paste - TypeScript/JavaScript', () => {
});
});
suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => {
suite('Auto Indent On Type - TypeScript/JavaScript', () => {
const languageId = "ts-test";
let disposables: DisposableStore;
@ -677,6 +755,248 @@ suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => {
});
});
test('issue #29755: do not add indentation on enter if indentation is already valid', () => {
//https://github.com/microsoft/vscode/issues/29755
const model = createTextModel([
'function f() {',
' const one = 1;',
' const two = 2;',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(3, 1, 3, 1));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(), [
'function f() {',
' const one = 1;',
'',
' const two = 2;',
'}',
].join('\n'));
});
});
test('issue #36090', () => {
// https://github.com/microsoft/vscode/issues/36090
const model = createTextModel([
'class ItemCtrl {',
' getPropertiesByItemId(id) {',
' return this.fetchItem(id)',
' .then(item => {',
' return this.getPropertiesOfItem(item);',
' });',
' }',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(7, 6, 7, 6));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(),
[
'class ItemCtrl {',
' getPropertiesByItemId(id) {',
' return this.fetchItem(id)',
' .then(item => {',
' return this.getPropertiesOfItem(item);',
' });',
' }',
' ',
'}',
].join('\n')
);
assert.deepStrictEqual(editor.getSelection(), new Selection(8, 5, 8, 5));
});
});
test('issue #115304: indent block comment onEnter', () => {
// https://github.com/microsoft/vscode/issues/115304
const model = createTextModel([
'/** */',
'function f() {}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(1, 4, 1, 4));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(),
[
'/**',
' * ',
' */',
'function f() {}',
].join('\n')
);
assert.deepStrictEqual(editor.getSelection(), new Selection(2, 4, 2, 4));
});
});
// Failing tests...
test('issue #43244: indent when lambda arrow function is detected, outdent when end is reached', () => {
// https://github.com/microsoft/vscode/issues/43244
const model = createTextModel([
'const array = [1, 2, 3, 4, 5];',
'array.map(_)'
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(2, 12, 2, 12));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'const array = [1, 2, 3, 4, 5];',
'array.map(_',
' ',
')'
].join('\n'));
});
});
test('issue #43244: incorrect indentation after if/for/while without braces', () => {
// https://github.com/microsoft/vscode/issues/43244
const model = createTextModel([
'function f() {',
' if (condition)',
'}'
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(2, 19, 2, 19));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'function f() {',
' if (condition)',
' ',
'}',
].join('\n'));
viewModel.type("return;");
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'function f() {',
' if (condition)',
' return;',
' ',
'}',
].join('\n'));
});
});
// Failing tests...
test('issue #29755: do not add indentation on enter if indentation is already valid', () => {
//https://github.com/microsoft/vscode/issues/29755
const model = createTextModel([
'function f() {',
' const one = 1;',
' const two = 2;',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(3, 1, 3, 1));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(), [
'function f() {',
' const one = 1;',
'',
' const two = 2;',
'}',
].join('\n'));
});
});
test('issue #36090', () => {
// https://github.com/microsoft/vscode/issues/36090
const model = createTextModel([
'class ItemCtrl {',
' getPropertiesByItemId(id) {',
' return this.fetchItem(id)',
' .then(item => {',
' return this.getPropertiesOfItem(item);',
' });',
' }',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(7, 6, 7, 6));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(),
[
'class ItemCtrl {',
' getPropertiesByItemId(id) {',
' return this.fetchItem(id)',
' .then(item => {',
' return this.getPropertiesOfItem(item);',
' });',
' }',
' ',
'}',
].join('\n')
);
assert.deepStrictEqual(editor.getSelection(), new Selection(8, 5, 8, 5));
});
});
test('issue #115304: indent block comment onEnter', () => {
// https://github.com/microsoft/vscode/issues/115304
const model = createTextModel([
'/** */',
'function f() {}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(1, 4, 1, 4));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(),
[
'/**',
' * ',
' */',
'function f() {}',
].join('\n')
);
assert.deepStrictEqual(editor.getSelection(), new Selection(2, 4, 2, 4));
});
});
test('issue #43244: indent when lambda arrow function is detected, outdent when end is reached', () => {
// https://github.com/microsoft/vscode/issues/43244

View file

@ -26,7 +26,6 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModelEventDispatcher';
import { ITestCodeEditor, TestCodeEditorInstantiationOptions, createCodeEditorServices, instantiateTestCodeEditor, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/onEnterRules';
import { IRelaxedTextModelCreationOptions, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
@ -4469,93 +4468,6 @@ suite('Editor Controller', () => {
});
});
test('issue #36090: JS: editor.autoIndent seems to be broken', () => {
const languageId = 'jsMode';
disposables.add(languageService.registerLanguage({ id: languageId }));
disposables.add(languageConfigurationService.register(languageId, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
indentationRules: {
// ^(.*\*/)?\s*\}.*$
decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/,
// ^.*\{[^}"']*$
increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/
},
onEnterRules: javascriptOnEnterRules
}));
const model = createTextModel(
[
'class ItemCtrl {',
' getPropertiesByItemId(id) {',
' return this.fetchItem(id)',
' .then(item => {',
' return this.getPropertiesOfItem(item);',
' });',
' }',
'}',
].join('\n'),
languageId
);
withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel) => {
moveTo(editor, viewModel, 7, 6, false);
assertCursor(viewModel, new Selection(7, 6, 7, 6));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(),
[
'class ItemCtrl {',
' getPropertiesByItemId(id) {',
' return this.fetchItem(id)',
' .then(item => {',
' return this.getPropertiesOfItem(item);',
' });',
' }',
' ',
'}',
].join('\n')
);
assertCursor(viewModel, new Selection(8, 5, 8, 5));
});
});
test('issue #115304: OnEnter broken for TS', () => {
const languageId = 'jsMode';
disposables.add(languageService.registerLanguage({ id: languageId }));
disposables.add(languageConfigurationService.register(languageId, {
onEnterRules: javascriptOnEnterRules
}));
const model = createTextModel(
[
'/** */',
'function f() {}',
].join('\n'),
languageId
);
withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel) => {
moveTo(editor, viewModel, 1, 4, false);
assertCursor(viewModel, new Selection(1, 4, 1, 4));
viewModel.type('\n', 'keyboard');
assert.strictEqual(model.getValue(),
[
'/**',
' * ',
' */',
'function f() {}',
].join('\n')
);
assertCursor(viewModel, new Selection(2, 4, 2, 4));
});
});
test('issue #38261: TAB key results in bizarre indentation in C++ mode ', () => {
const languageId = 'indentRulesMode';

View file

@ -327,7 +327,7 @@ export class WebClientServer {
const workbenchWebConfiguration = {
remoteAuthority,
remoteBaseUrl: this._basePath,
serverBasePath: this._basePath,
_wrapWebWorkerExtHostInIframe,
developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() },
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,

View file

@ -660,17 +660,27 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return true;
}
private toResourceTelemetryDescriptor(resource: URI): object | undefined {
if (!resource) {
return undefined;
}
const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined;
if (!path) {
return undefined;
}
let resourceExt = extname(resource);
// Remove query parameters from the resource extension
const queryStringLocation = resourceExt.indexOf('?');
resourceExt = queryStringLocation !== -1 ? resourceExt.substr(0, queryStringLocation) : resourceExt;
return { mimeType: new TelemetryTrustedValue(getMimeTypes(resource).join(', ')), scheme: resource.scheme, ext: resourceExt, path: hash(path) };
}
private toEditorTelemetryDescriptor(editor: EditorInput): object {
const descriptor = editor.getTelemetryDescriptor();
const resource = EditorResourceAccessor.getOriginalUri(editor);
const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined;
if (resource && path) {
let resourceExt = extname(resource);
// Remove query parameters from the resource extension
const queryStringLocation = resourceExt.indexOf('?');
resourceExt = queryStringLocation !== -1 ? resourceExt.substr(0, queryStringLocation) : resourceExt;
descriptor['resource'] = { mimeType: new TelemetryTrustedValue(getMimeTypes(resource).join(', ')), scheme: resource.scheme, ext: resourceExt, path: hash(path) };
const resource = EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH });
if (URI.isUri(resource)) {
descriptor['resource'] = this.toResourceTelemetryDescriptor(resource);
/* __GDPR__FRAGMENT__
"EditorTelemetryDescriptor" : {
@ -678,6 +688,20 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
*/
return descriptor;
} else if (resource) {
if (resource.primary) {
descriptor['resource'] = this.toResourceTelemetryDescriptor(resource.primary);
}
if (resource.secondary) {
descriptor['resourceSecondary'] = this.toResourceTelemetryDescriptor(resource.secondary);
}
/* __GDPR__FRAGMENT__
"EditorTelemetryDescriptor" : {
"resource": { "${inline}": [ "${URIDescriptor}" ] },
"resourceSecondary": { "${inline}": [ "${URIDescriptor}" ] }
}
*/
return descriptor;
}
return descriptor;

View file

@ -31,7 +31,6 @@ import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeB
import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@ -423,11 +422,6 @@ export function registerChatCodeBlockActions() {
CONTEXT_IN_CHAT_SESSION,
...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e))
)
},
{
id: MenuId.ChatCodeBlock,
group: 'navigation',
when: CTX_INLINE_CHAT_VISIBLE,
}],
keybinding: [{
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter,

View file

@ -178,7 +178,10 @@ export function getQuickChatActionForProvider(id: string, label: string) {
override run(accessor: ServicesAccessor, query?: string): void {
const quickChatService = accessor.get(IQuickChatService);
quickChatService.toggle(id, query ? { query } : undefined);
quickChatService.toggle(id, query ? {
query,
selection: new Selection(1, query.length + 1, 1, query.length + 1)
} : undefined);
}
};
}

View file

@ -267,7 +267,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable {
const agentText = (await Promise.all(agents
.filter(a => a.id !== defaultAgent?.id)
.map(async a => {
const agentWithLeader = `${chatAgentLeader}${a.id}`;
const agentWithLeader = `${chatAgentLeader}${a.name}`;
const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` };
const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg));
const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.description}`;

View file

@ -45,7 +45,13 @@ export class ChatFollowups<T extends IChatFollowup | IInlineChatFollowup> extend
let tooltipPrefix = '';
if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent(this.location)?.id) {
tooltipPrefix += `${chatAgentLeader}${followup.agentId} `;
const agent = this.chatAgentService.getAgent(followup.agentId);
if (!agent) {
// Refers to agent that doesn't exist
return;
}
tooltipPrefix += `${chatAgentLeader}${agent.name} `;
if ('subCommand' in followup && followup.subCommand) {
tooltipPrefix += `${chatSubcommandLeader}${followup.subCommand} `;
}

View file

@ -504,7 +504,7 @@ class SubmitButtonActionViewItem extends ActionViewItem {
if (secondaryAgent) {
const secondaryKeybinding = keybindingService.lookupKeybinding(ChatSubmitSecondaryAgentEditorAction.ID)?.getLabel();
if (secondaryKeybinding) {
tooltip += `\n${chatAgentLeader}${secondaryAgent.id} (${secondaryKeybinding})`;
tooltip += `\n${chatAgentLeader}${secondaryAgent.name} (${secondaryKeybinding})`;
}
}

View file

@ -77,10 +77,12 @@ export class QuickChatService extends Disposable implements IQuickChatService {
open(providerId?: string, options?: IQuickChatOpenOptions): void {
if (this._input) {
if (this._currentChat && options?.query) {
this._currentChat.focus();
this._currentChat.setValue(options.query, options.selection);
if (!options.isPartialQuery) {
this._currentChat.acceptInput();
}
return;
}
return this.focus();
}

View file

@ -211,7 +211,7 @@ class InputEditorDecorations extends Disposable {
const textDecorations: IDecorationOptions[] | undefined = [];
if (agentPart) {
const agentHover = `(${agentPart.agent.id}) ${agentPart.agent.description}`;
const agentHover = `(${agentPart.agent.name}) ${agentPart.agent.description}`;
textDecorations.push({ range: agentPart.editorRange, hoverMessage: new MarkdownString(agentHover) });
if (agentSubcommandPart) {
textDecorations.push({ range: agentSubcommandPart.editorRange, hoverMessage: new MarkdownString(agentSubcommandPart.command.description) });

View file

@ -120,8 +120,8 @@ export class InlineChatContentWidget implements IContentWidget {
beforeRender(): IDimension | null {
const contentWidth = this._editor.getLayoutInfo().contentWidth;
const minWidth = Math.round(contentWidth * 0.33);
const maxWidth = Math.round(contentWidth * 0.66);
const minWidth = Math.round(contentWidth * 0.38);
const maxWidth = Math.round(contentWidth * 0.82);
const width = clamp(220, minWidth, maxWidth);

View file

@ -62,6 +62,15 @@
font-size: 11px;
align-self: baseline;
padding-left: 10px;
display: inline-flex;
}
.monaco-workbench .inline-chat .status .label.info {
margin-right: auto;
padding-left: 2px;
}
.monaco-workbench .inline-chat .status .label.status {
margin-left: auto;
}

View file

@ -357,7 +357,7 @@ const COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.collapseAllCellOutpu
const EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.expandAllCellOutputs';
const TOGGLE_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.toggleOutputs';
const TOGGLE_CELL_OUTPUT_SCROLLING = 'notebook.cell.toggleOutputScrolling';
const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions';
export const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions';
registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction {
constructor() {
@ -601,7 +601,7 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookCellAction {
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
if (context.cell instanceof CodeCellViewModel) {
const error = context.cell.cellErrorDetails;
const error = context.cell.cellDiagnostics.ErrorDetails;
if (error?.location) {
const location = Range.lift({
startLineNumber: error.location.startLineNumber + 1,

View file

@ -12,10 +12,14 @@ import { Iterable } from 'vs/base/common/iterator';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { Emitter, Event } from 'vs/base/common/event';
export class CellDiagnostics extends Disposable {
private readonly _onDidDiagnosticsChange = new Emitter<void>();
readonly onDidDiagnosticsChange: Event<void> = this._onDidDiagnosticsChange.event;
static ID: string = 'workbench.notebook.cellDiagnostics';
private enabled = false;
@ -48,7 +52,7 @@ export class CellDiagnostics extends Disposable {
const settingEnabled = this.configurationService.getValue(NotebookSetting.cellFailureDiagnostics);
if (this.enabled && (!settingEnabled || Iterable.isEmpty(this.inlineChatService.getAllProvider()))) {
this.enabled = false;
this.clearDiagnostics();
this.clear();
} else if (!this.enabled && settingEnabled && !Iterable.isEmpty(this.inlineChatService.getAllProvider())) {
this.enabled = true;
if (!this.listening) {
@ -62,7 +66,7 @@ export class CellDiagnostics extends Disposable {
if (this.enabled && e.type === NotebookExecutionType.cell && e.affectsCell(this.cell.uri)) {
if (!!e.changed) {
// cell is running
this.clearDiagnostics();
this.clear();
} else {
this.setDiagnostics();
}
@ -71,21 +75,19 @@ export class CellDiagnostics extends Disposable {
public clear() {
if (this.ErrorDetails) {
this.clearDiagnostics();
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []);
this.errorDetails = undefined;
this._onDidDiagnosticsChange.fire();
}
}
private clearDiagnostics() {
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []);
this.errorDetails = undefined;
}
private setDiagnostics() {
const metadata = this.cell.model.internalMetadata;
if (!metadata.lastRunSuccess && metadata?.error?.location) {
const marker = this.createMarkerData(metadata.error.message, metadata.error.location);
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, [marker]);
this.errorDetails = metadata.error;
this._onDidDiagnosticsChange.fire();
}
}

View file

@ -19,6 +19,9 @@ import { CellStatusbarAlignment, INotebookCellStatusBarItem, NotebookCellExecuti
import { INotebookCellExecution, INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export function formatCellDuration(duration: number, showMilliseconds: boolean = true): string {
if (showMilliseconds && duration < 1000) {
@ -333,3 +336,60 @@ class TimerCellStatusBarItem extends Disposable {
this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]);
}
}
export class DiagnosticCellStatusBarContrib extends Disposable implements INotebookEditorContribution {
static id: string = 'workbench.notebook.statusBar.diagtnostic';
constructor(
notebookEditor: INotebookEditor,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
this._register(new NotebookStatusBarController(notebookEditor, (vm, cell) =>
cell instanceof CodeCellViewModel ?
instantiationService.createInstance(DiagnosticCellStatusBarItem, vm, cell) :
Disposable.None
));
}
}
registerNotebookContribution(DiagnosticCellStatusBarContrib.id, DiagnosticCellStatusBarContrib);
class DiagnosticCellStatusBarItem extends Disposable {
private _currentItemIds: string[] = [];
constructor(
private readonly _notebookViewModel: INotebookViewModel,
private readonly cell: CodeCellViewModel,
@IKeybindingService private readonly keybindingService: IKeybindingService
) {
super();
this._update();
this._register(this.cell.cellDiagnostics.onDidDiagnosticsChange(() => this._update()));
}
private async _update() {
let item: INotebookCellStatusBarItem | undefined;
if (!!this.cell.cellDiagnostics.ErrorDetails) {
const keybinding = this.keybindingService.lookupKeybinding(OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID)?.getLabel();
const tooltip = localize('notebook.cell.status.diagnostic', "Quick Actions {0}", `(${keybinding})`);
item = {
text: `$(sparkle)`,
tooltip,
alignment: CellStatusbarAlignment.Left,
command: OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID,
priority: Number.MAX_SAFE_INTEGER - 1
};
}
const items = item ? [item] : [];
this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items }]);
}
override dispose() {
super.dispose();
this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items: [] }]);
}
}

View file

@ -178,6 +178,7 @@ class NotebookOutlineRenderer implements ITreeRenderer<OutlineEntry, FuzzyScore,
outlineEntryToolbar.setActions(actions.primary, actions.secondary);
this.setupToolbarListeners(outlineEntryToolbar, menu, actions, node.element, template);
template.actionMenu.style.padding = '0 0.8em 0 0.4em';
}
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { localize, localize2 } from 'vs/nls';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
@ -386,6 +386,18 @@ registerAction2(class extends NotebookAction {
]
},
f1: false,
keybinding: {
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_FOCUSED,
NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true),
ContextKeyExpr.not(InputFocusedContextKey),
CTX_INLINE_CHAT_HAS_PROVIDER,
ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true)
),
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyCode.KeyI,
secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)],
},
menu: [
{
id: MenuId.NotebookCellBetween,

View file

@ -1101,9 +1101,9 @@ configurationRegistry.registerConfiguration({
default: false
},
[NotebookSetting.cellFailureDiagnostics]: {
markdownDescription: nls.localize('notebook.cellFailureDiagnostics', "Enable experimental diagnostics for cell failures."),
markdownDescription: nls.localize('notebook.cellFailureDiagnostics', "Show available diagnostics for cell failures."),
type: 'boolean',
default: false
default: true
},
}
});

View file

@ -101,6 +101,7 @@ export class CellContextKeyManager extends Disposable {
if (element instanceof CodeCellViewModel) {
this.elementDisposables.add(element.onDidChangeOutputs(() => this.updateForOutputs()));
this.elementDisposables.add(element.cellDiagnostics.onDidDiagnosticsChange(() => this.updateForDiagnostics()));
}
this.elementDisposables.add(this.notebookEditor.onDidChangeActiveCell(() => this.updateForFocusState()));
@ -118,6 +119,7 @@ export class CellContextKeyManager extends Disposable {
this.updateForCollapseState();
this.updateForOutputs();
this.updateForChat();
this.updateForDiagnostics();
this.cellLineNumbers.set(this.element!.lineNumbers);
this.cellResource.set(this.element!.uri.toString());
@ -202,10 +204,6 @@ export class CellContextKeyManager extends Disposable {
this.cellRunState.set('idle');
this.cellExecuting.set(false);
}
if (this.element instanceof CodeCellViewModel) {
this.cellHasErrorDiagnostics.set(!!this.element.cellErrorDetails);
}
}
private updateForEditState() {
@ -247,4 +245,10 @@ export class CellContextKeyManager extends Disposable {
this.cellGeneratedByChat.set(chatController.isCellGeneratedByChat(this.element));
}
private updateForDiagnostics() {
if (this.element instanceof CodeCellViewModel) {
this.cellHasErrorDiagnostics.set(!!this.element.cellDiagnostics.ErrorDetails);
}
}
}

View file

@ -521,13 +521,49 @@ async function webviewPreloads(ctx: PreloadContext) {
}
};
let previousDelta: number | undefined;
let scrollTimeout: any /* NodeJS.Timeout */ | undefined;
let scrolledElement: Element | undefined;
function flagRecentlyScrolled(node: Element) {
let lastTimeScrolled: number | undefined;
function flagRecentlyScrolled(node: Element, deltaY?: number) {
scrolledElement = node;
node.setAttribute('recentlyScrolled', 'true');
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 300);
if (!deltaY) {
lastTimeScrolled = Date.now();
previousDelta = undefined;
node.setAttribute('recentlyScrolled', 'true');
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 300);
return true;
}
if (node.hasAttribute('recentlyScrolled')) {
if (lastTimeScrolled && Date.now() - lastTimeScrolled > 300) {
// it has been a while since we actually scrolled
// if scroll velocity increases, it's likely a new scroll event
if (!!previousDelta && deltaY < 0 && deltaY < previousDelta - 2) {
clearTimeout(scrollTimeout);
scrolledElement?.removeAttribute('recentlyScrolled');
return false;
} else if (!!previousDelta && deltaY > 0 && deltaY > previousDelta + 2) {
clearTimeout(scrollTimeout);
scrolledElement?.removeAttribute('recentlyScrolled');
return false;
}
// the tail end of a smooth scrolling event (from a trackpad) can go on for a while
// so keep swallowing it, but we can shorten the timeout since the events occur rapidly
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 50);
} else {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 300);
}
previousDelta = deltaY;
return true;
}
return false;
}
function eventTargetShouldHandleScroll(event: WheelEvent) {
@ -536,11 +572,6 @@ async function webviewPreloads(ctx: PreloadContext) {
return false;
}
if (node.hasAttribute('recentlyScrolled') && scrolledElement === node) {
flagRecentlyScrolled(node);
return true;
}
// scroll up
if (event.deltaY < 0 && node.scrollTop > 0) {
// there is still some content to scroll
@ -565,6 +596,10 @@ async function webviewPreloads(ctx: PreloadContext) {
flagRecentlyScrolled(node);
return true;
}
if (flagRecentlyScrolled(node, event.deltaY)) {
return true;
}
}
return false;

View file

@ -47,8 +47,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
private _outputCollection: number[] = [];
private readonly _cellDiagnostics: CellDiagnostics;
get cellErrorDetails() {
return this._cellDiagnostics.ErrorDetails;
get cellDiagnostics() {
return this._cellDiagnostics;
}
private _outputsTop: PrefixSumComputer | null = null;
@ -436,10 +436,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
protected onDidChangeTextModelContent(): void {
if (this.getEditState() !== CellEditState.Editing) {
this._cellDiagnostics.clear();
this.updateEditState(CellEditState.Editing, 'onDidChangeTextModelContent');
this._onDidChangeState.fire({ contentChanged: true });
}
this._cellDiagnostics.clear();
}
onDeselect() {

View file

@ -956,7 +956,7 @@ export const NotebookSetting = {
cellChat: 'notebook.experimental.cellChat',
notebookVariablesView: 'notebook.experimental.variablesView',
InteractiveWindowPromptToSave: 'interactiveWindow.promptToSaveOnClose',
cellFailureDiagnostics: 'notebook.experimental.cellFailureDiagnostics',
cellFailureDiagnostics: 'notebook.cellFailureDiagnostics',
} as const;
export const enum CellStatusbarAlignment {

View file

@ -125,7 +125,6 @@ export class SearchView extends ViewPane {
private hasReplacePatternKey: IContextKey<boolean>;
private hasFilePatternKey: IContextKey<boolean>;
private hasSomeCollapsibleResultKey: IContextKey<boolean>;
private hasAIResultProvider: IContextKey<boolean>;
private tree!: WorkbenchCompressibleObjectTree<RenderableMatch>;
private treeLabels!: ResourceLabels;
@ -221,10 +220,10 @@ export class SearchView extends ViewPane {
this.hasSomeCollapsibleResultKey = Constants.SearchContext.ViewHasSomeCollapsibleKey.bindTo(this.contextKeyService);
this.treeViewKey = Constants.SearchContext.InTreeViewKey.bindTo(this.contextKeyService);
this.aiResultsVisibleKey = Constants.SearchContext.AIResultsVisibleKey.bindTo(this.contextKeyService);
this.hasAIResultProvider = Constants.SearchContext.hasAIResultProvider.bindTo(this.contextKeyService);
this._register(this.contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(new Set(Constants.SearchContext.hasAIResultProvider.keys()))) {
const keys = Constants.SearchContext.hasAIResultProvider.keys();
if (e.affectsSome(new Set(keys))) {
this.refreshHasAISetting();
}
}));
@ -436,6 +435,7 @@ export class SearchView extends ViewPane {
this.searchWidgetsContainerElement = dom.append(this.container, $('.search-widgets-container'));
this.createSearchWidget(this.searchWidgetsContainerElement);
this.refreshHasAISetting();
const history = this.searchHistoryService.load();
const filePatterns = this.viewletState['query.filePatterns'] || '';
@ -680,7 +680,8 @@ export class SearchView extends ViewPane {
}
private shouldShowAIButton(): boolean {
return !!(this.configurationService.getValue<boolean>('search.aiResults') && this.hasAIResultProvider.get());
const hasProvider = Constants.SearchContext.hasAIResultProvider.getValue(this.contextKeyService);
return !!(this.configurationService.getValue<boolean>('search.aiResults') && hasProvider);
}
private onConfigurationUpdated(event?: IConfigurationChangeEvent): void {
if (event && (event.affectsConfiguration('search.decorations.colors') || event.affectsConfiguration('search.decorations.badges'))) {

View file

@ -1776,6 +1776,15 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) {
type: 'string',
enum: profileEnum.values,
markdownEnumDescriptions: profileEnum.markdownDescriptions
},
location: {
description: localize('newWithProfile.location', "Where to create the terminal"),
type: 'string',
enum: ['view', 'editor'],
enumDescriptions: [
localize('newWithProfile.location.view', 'Create the terminal in the terminal view'),
localize('newWithProfile.location.editor', 'Create the terminal in the editor'),
]
}
}
}
@ -1783,7 +1792,11 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) {
},
});
}
async run(accessor: ServicesAccessor, eventOrOptionsOrProfile: MouseEvent | ICreateTerminalOptions | ITerminalProfile | { profileName: string } | undefined, profile?: ITerminalProfile) {
async run(
accessor: ServicesAccessor,
eventOrOptionsOrProfile: MouseEvent | ICreateTerminalOptions | ITerminalProfile | { profileName: string; location?: 'view' | 'editor' | unknown } | undefined,
profile?: ITerminalProfile
) {
const c = getTerminalServices(accessor);
const workspaceContextService = accessor.get(IWorkspaceContextService);
const commandService = accessor.get(ICommandService);
@ -1799,6 +1812,12 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) {
throw new Error(`Could not find terminal profile "${eventOrOptionsOrProfile.profileName}"`);
}
options = { config };
if ('location' in eventOrOptionsOrProfile) {
switch (eventOrOptionsOrProfile.location) {
case 'editor': options.location = TerminalLocation.Editor; break;
case 'view': options.location = TerminalLocation.Panel; break;
}
}
} else if (isMouseEvent(eventOrOptionsOrProfile) || isPointerEvent(eventOrOptionsOrProfile) || isKeyboardEvent(eventOrOptionsOrProfile)) {
event = eventOrOptionsOrProfile;
options = profile ? { config: profile } : undefined;

View file

@ -824,10 +824,29 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
async runCommand(commandLine: string, shouldExecute: boolean): Promise<void> {
let commandDetection = this.capabilities.get(TerminalCapability.CommandDetection);
// Await command detection if the terminal is starting up
if (!commandDetection && (this._processManager.processState === ProcessState.Uninitialized || this._processManager.processState === ProcessState.Launching)) {
const store = new DisposableStore();
await Promise.race([
new Promise<void>(r => {
store.add(this.capabilities.onDidAddCapabilityType(e => {
if (e === TerminalCapability.CommandDetection) {
commandDetection = this.capabilities.get(TerminalCapability.CommandDetection);
r();
}
}));
}),
timeout(2000),
]);
store.dispose();
}
// Determine whether to send ETX (ctrl+c) before running the command. This should always
// happen unless command detection can reliably say that a command is being entered and
// there is no content in the prompt
if (this.capabilities.get(TerminalCapability.CommandDetection)?.hasInput !== false) {
if (commandDetection?.hasInput !== false) {
await this.sendText('\x03', false);
// Wait a little before running the command to avoid the sequences being echoed while the ^C
// is being evaluated

View file

@ -6,7 +6,7 @@
import { Codicon } from 'vs/base/common/codicons';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IQuickInputService, IKeyMods, IPickOptions, IQuickPickSeparator, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IExtensionTerminalProfile, ITerminalProfile, ITerminalProfileObject, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal';
import { IExtensionTerminalProfile, ITerminalProfile, ITerminalProfileObject, TerminalSettingPrefix, type ITerminalExecutable } from 'vs/platform/terminal/common/terminal';
import { getUriClasses, getColorClass, createColorStyleElement } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
import * as nls from 'vs/nls';
@ -131,11 +131,20 @@ export class TerminalProfileQuickpick {
if (!name) {
return;
}
const newConfigValue: { [key: string]: ITerminalProfileObject } = { ...configProfiles };
newConfigValue[name] = {
path: context.item.profile.path,
args: context.item.profile.args
};
const newConfigValue: { [key: string]: ITerminalExecutable } = { ...configProfiles };
newConfigValue[name] = { path: context.item.profile.path };
if (context.item.profile.args) {
newConfigValue[name].args = context.item.profile.args;
}
if (context.item.profile.env) {
newConfigValue[name].env = context.item.profile.env;
}
if (context.item.profile.color) {
newConfigValue[name].color = context.item.profile.color;
}
if (context.item.profile.icon) {
newConfigValue[name].icon = context.item.profile.icon;
}
await this._configurationService.updateValue(profilesKey, newConfigValue, ConfigurationTarget.USER);
},
onKeyMods: mods => keyMods = mods

View file

@ -563,7 +563,7 @@ const terminalConfiguration: IConfigurationNode = {
[TerminalSettingId.RescaleOverlappingGlyphs]: {
markdownDescription: localize('terminal.integrated.rescaleOverlappingGlyphs', "Whether to rescale glyphs horizontally that are a single cell wide but have glyphs that would overlap following cell(s). This typically happens for ambiguous width characters (eg. the roman numeral characters U+2160+) which aren't featured in monospace fonts. Emoji glyphs are never rescaled."),
type: 'boolean',
default: true
default: false
},
[TerminalSettingId.AutoReplies]: {
markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'),

View file

@ -59,6 +59,7 @@ export class TerminalStickyScrollOverlay extends Disposable {
private _refreshListeners = this._register(new MutableDisposable());
private _state: OverlayState = OverlayState.Off;
private _isRefreshQueued = false;
private _rawMaxLineCount: number = 5;
constructor(
@ -114,7 +115,7 @@ export class TerminalStickyScrollOverlay extends Disposable {
}));
this._register(this._xterm.raw.onResize(() => {
this._syncOptions();
this._throttledRefresh();
this._refresh();
}));
this._getSerializeAddonConstructor().then(SerializeAddon => {
@ -175,37 +176,15 @@ export class TerminalStickyScrollOverlay extends Disposable {
this._element?.classList.toggle(CssClasses.Visible, isVisible);
}
/**
* The entry point to refresh sticky scroll. This is synchronous and will call into the method
* that actually refreshes using either debouncing or throttling depending on the situation.
*
* The goal is that if the command has changed to update immediately (with throttling) and if
* the command is the same then update with debouncing as it's less likely updates will show up.
* This approach also helps with:
*
* - Cursor move only updates such as moving horizontally in pagers which without this may show
* the sticky scroll before hiding it again almost immediately due to everything not being
* parsed yet.
* - Improving performance due to deferring less important updates via debouncing.
* - Less flickering when scrolling, while still updating immediately when the command changes.
*/
private _refresh(): void {
if (!this._xterm.raw.element?.parentElement || !this._stickyScrollOverlay || !this._serializeAddon) {
if (this._isRefreshQueued) {
return;
}
const command = this._commandDetection.getCommandForLine(this._xterm.raw.buffer.active.viewportY);
if (command && this._currentStickyCommand !== command) {
this._throttledRefresh();
} else {
// If it's the same command, do not throttle as the sticky scroll overlay height may
// need to be adjusted. This would cause a flicker if throttled.
this._isRefreshQueued = true;
queueMicrotask(() => {
this._refreshNow();
}
}
@throttle(0)
private _throttledRefresh(): void {
this._refreshNow();
this._isRefreshQueued = false;
});
}
private _refreshNow(): void {