mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
Merge branch 'main' into aiday/indentationTestsAccrossDifferentLanguages
This commit is contained in:
commit
4e98d4b801
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
Binary file not shown.
|
@ -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,
|
||||
|
|
|
@ -660,17 +660,27 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
|||
return true;
|
||||
}
|
||||
|
||||
private toEditorTelemetryDescriptor(editor: EditorInput): object {
|
||||
const descriptor = editor.getTelemetryDescriptor();
|
||||
|
||||
const resource = EditorResourceAccessor.getOriginalUri(editor);
|
||||
private toResourceTelemetryDescriptor(resource: URI): object | undefined {
|
||||
if (!resource) {
|
||||
return undefined;
|
||||
}
|
||||
const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined;
|
||||
if (resource && path) {
|
||||
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;
|
||||
descriptor['resource'] = { mimeType: new TelemetryTrustedValue(getMimeTypes(resource).join(', ')), scheme: resource.scheme, ext: resourceExt, path: hash(path) };
|
||||
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, { 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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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} `;
|
||||
}
|
||||
|
|
|
@ -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})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) });
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,13 +75,10 @@ export class CellDiagnostics extends Disposable {
|
|||
|
||||
public clear() {
|
||||
if (this.ErrorDetails) {
|
||||
this.clearDiagnostics();
|
||||
}
|
||||
}
|
||||
|
||||
private clearDiagnostics() {
|
||||
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []);
|
||||
this.errorDetails = undefined;
|
||||
this._onDidDiagnosticsChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
private setDiagnostics() {
|
||||
|
@ -86,6 +87,7 @@ export class CellDiagnostics extends Disposable {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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: [] }]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'))) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`'),
|
||||
|
|
|
@ -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._refreshNow();
|
||||
}
|
||||
}
|
||||
|
||||
@throttle(0)
|
||||
private _throttledRefresh(): void {
|
||||
this._isRefreshQueued = true;
|
||||
queueMicrotask(() => {
|
||||
this._refreshNow();
|
||||
this._isRefreshQueued = false;
|
||||
});
|
||||
}
|
||||
|
||||
private _refreshNow(): void {
|
||||
|
|
Loading…
Reference in a new issue