Path: blob/main/src/vs/workbench/contrib/debug/browser/callStackWidget.ts
5236 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../../base/browser/dom.js';6import { Button } from '../../../../base/browser/ui/button/button.js';7import { IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';8import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';9import { assertNever } from '../../../../base/common/assert.js';10import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';11import { Codicon } from '../../../../base/common/codicons.js';12import { Emitter, Event } from '../../../../base/common/event.js';13import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';14import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue, transaction } from '../../../../base/common/observable.js';15import { ThemeIcon } from '../../../../base/common/themables.js';16import { Constants } from '../../../../base/common/uint.js';17import { URI } from '../../../../base/common/uri.js';18import { generateUuid } from '../../../../base/common/uuid.js';19import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';20import { EditorContributionCtor, EditorContributionInstantiation, IEditorContributionDescription } from '../../../../editor/browser/editorExtensions.js';21import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';22import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js';23import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js';24import { Position } from '../../../../editor/common/core/position.js';25import { Range } from '../../../../editor/common/core/range.js';26import { IWordAtPosition } from '../../../../editor/common/core/wordHelper.js';27import { IEditorContribution, IEditorDecorationsCollection } from '../../../../editor/common/editorCommon.js';28import { Location } from '../../../../editor/common/languages.js';29import { ITextModelService } from '../../../../editor/common/services/resolverService.js';30import { ClickLinkGesture, ClickLinkMouseEvent } from '../../../../editor/contrib/gotoSymbol/browser/link/clickLinkGesture.js';31import { localize, localize2 } from '../../../../nls.js';32import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';33import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';34import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';35import { TextEditorSelectionRevealType } from '../../../../platform/editor/common/editor.js';36import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';37import { ILabelService } from '../../../../platform/label/common/label.js';38import { WorkbenchList } from '../../../../platform/list/browser/listService.js';39import { INotificationService } from '../../../../platform/notification/common/notification.js';40import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js';41import { ResourceLabel } from '../../../browser/labels.js';42import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';43import { makeStackFrameColumnDecoration, TOP_STACK_FRAME_DECORATION } from './callStackEditorContribution.js';44import './media/callStackWidget.css';454647export class CallStackFrame {48constructor(49public readonly name: string,50public readonly source?: URI,51public readonly line = 1,52public readonly column = 1,53) { }54}5556export class SkippedCallFrames {57constructor(58public readonly label: string,59public readonly load: (token: CancellationToken) => Promise<AnyStackFrame[]>,60) { }61}6263export abstract class CustomStackFrame {64public readonly showHeader = observableValue('CustomStackFrame.showHeader', true);65public abstract readonly height: IObservable<number>;66public abstract readonly label: string;67public icon?: ThemeIcon;68public abstract render(container: HTMLElement): IDisposable;69public renderActions?(container: HTMLElement): IDisposable;70}7172export type AnyStackFrame = SkippedCallFrames | CallStackFrame | CustomStackFrame;7374interface IFrameLikeItem {75readonly collapsed: ISettableObservable<boolean>;76readonly height: IObservable<number>;77}7879class WrappedCallStackFrame extends CallStackFrame implements IFrameLikeItem {80public readonly editorHeight = observableValue('WrappedCallStackFrame.height', this.source ? 100 : 0);81public readonly collapsed = observableValue('WrappedCallStackFrame.collapsed', false);8283public readonly height = derived(reader => {84return this.collapsed.read(reader) ? CALL_STACK_WIDGET_HEADER_HEIGHT : CALL_STACK_WIDGET_HEADER_HEIGHT + this.editorHeight.read(reader);85});8687constructor(original: CallStackFrame) {88super(original.name, original.source, original.line, original.column);89}90}9192class WrappedCustomStackFrame implements IFrameLikeItem {93public readonly collapsed = observableValue('WrappedCallStackFrame.collapsed', false);9495public readonly height = derived(reader => {96const headerHeight = this.original.showHeader.read(reader) ? CALL_STACK_WIDGET_HEADER_HEIGHT : 0;97return this.collapsed.read(reader) ? headerHeight : headerHeight + this.original.height.read(reader);98});99100constructor(public readonly original: CustomStackFrame) { }101}102103const isFrameLike = (item: unknown): item is IFrameLikeItem =>104item instanceof WrappedCallStackFrame || item instanceof WrappedCustomStackFrame;105106type ListItem = WrappedCallStackFrame | SkippedCallFrames | WrappedCustomStackFrame;107108const WIDGET_CLASS_NAME = 'multiCallStackWidget';109110/**111* A reusable widget that displays a call stack as a series of editors. Note112* that this both used in debug's exception widget as well as in the testing113* call stack view.114*/115export class CallStackWidget extends Disposable {116private readonly list: WorkbenchList<ListItem>;117private readonly layoutEmitter = this._register(new Emitter<void>());118private readonly currentFramesDs = this._register(new DisposableStore());119private cts?: CancellationTokenSource;120121public get onDidChangeContentHeight() {122return this.list.onDidChangeContentHeight;123}124125public get onDidScroll() {126return this.list.onDidScroll;127}128129public get contentHeight() {130return this.list.contentHeight;131}132133constructor(134container: HTMLElement,135containingEditor: ICodeEditor | undefined,136@IInstantiationService instantiationService: IInstantiationService,137) {138super();139140container.classList.add(WIDGET_CLASS_NAME);141this._register(toDisposable(() => container.classList.remove(WIDGET_CLASS_NAME)));142143this.list = this._register(instantiationService.createInstance(144WorkbenchList,145'TestResultStackWidget',146container,147new StackDelegate(),148[149instantiationService.createInstance(FrameCodeRenderer, containingEditor, this.layoutEmitter.event),150instantiationService.createInstance(MissingCodeRenderer),151instantiationService.createInstance(CustomRenderer),152instantiationService.createInstance(SkippedRenderer, (i) => this.loadFrame(i)),153],154{155multipleSelectionSupport: false,156mouseSupport: false,157keyboardSupport: false,158setRowLineHeight: false,159alwaysConsumeMouseWheel: false,160accessibilityProvider: instantiationService.createInstance(StackAccessibilityProvider),161}162) as WorkbenchList<ListItem>);163}164165/** Replaces the call frames display in the view. */166public setFrames(frames: AnyStackFrame[]): void {167// cancel any existing load168this.currentFramesDs.clear();169const cts = new CancellationTokenSource();170this.currentFramesDs.add(toDisposable(() => cts.dispose(true)));171this.cts = cts;172173this.list.splice(0, this.list.length, this.mapFrames(frames));174}175176public layout(height?: number, width?: number): void {177this.list.layout(height, width);178this.layoutEmitter.fire();179}180181public collapseAll() {182transaction(tx => {183for (let i = 0; i < this.list.length; i++) {184const frame = this.list.element(i);185if (isFrameLike(frame)) {186frame.collapsed.set(true, tx);187}188}189});190}191192private async loadFrame(replacing: SkippedCallFrames): Promise<void> {193if (!this.cts) {194return;195}196197const frames = await replacing.load(this.cts.token);198if (this.cts.token.isCancellationRequested) {199return;200}201202const index = this.list.indexOf(replacing);203this.list.splice(index, 1, this.mapFrames(frames));204}205206private mapFrames(frames: AnyStackFrame[]): ListItem[] {207const result: ListItem[] = [];208for (const frame of frames) {209if (frame instanceof SkippedCallFrames) {210result.push(frame);211continue;212}213214const wrapped = frame instanceof CustomStackFrame215? new WrappedCustomStackFrame(frame) : new WrappedCallStackFrame(frame);216result.push(wrapped);217218this.currentFramesDs.add(autorun(reader => {219const height = wrapped.height.read(reader);220const idx = this.list.indexOf(wrapped);221if (idx !== -1) {222this.list.updateElementHeight(idx, height);223}224}));225}226227return result;228}229}230231class StackAccessibilityProvider implements IListAccessibilityProvider<ListItem> {232constructor(@ILabelService private readonly labelService: ILabelService) { }233234getAriaLabel(e: ListItem): string | IObservable<string> | null {235if (e instanceof SkippedCallFrames) {236return e.label;237}238239if (e instanceof WrappedCustomStackFrame) {240return e.original.label;241}242243if (e instanceof CallStackFrame) {244if (e.source && e.line) {245return localize({246comment: ['{0} is an extension-defined label, then line number and filename'],247key: 'stackTraceLabel',248}, '{0}, line {1} in {2}', e.name, e.line, this.labelService.getUriLabel(e.source, { relative: true }));249}250251return e.name;252}253254assertNever(e);255}256getWidgetAriaLabel(): string {257return localize('stackTrace', 'Stack Trace');258}259}260261class StackDelegate implements IListVirtualDelegate<ListItem> {262getHeight(element: ListItem): number {263if (element instanceof CallStackFrame || element instanceof WrappedCustomStackFrame) {264return element.height.get();265}266if (element instanceof SkippedCallFrames) {267return CALL_STACK_WIDGET_HEADER_HEIGHT;268}269270assertNever(element);271}272273getTemplateId(element: ListItem): string {274if (element instanceof CallStackFrame) {275return element.source ? FrameCodeRenderer.templateId : MissingCodeRenderer.templateId;276}277if (element instanceof SkippedCallFrames) {278return SkippedRenderer.templateId;279}280if (element instanceof WrappedCustomStackFrame) {281return CustomRenderer.templateId;282}283284assertNever(element);285}286}287288interface IStackTemplateData extends IAbstractFrameRendererTemplateData {289editor: CodeEditorWidget;290toolbar: MenuWorkbenchToolBar;291}292293const editorOptions: IEditorOptions = {294scrollBeyondLastLine: false,295scrollbar: {296vertical: 'hidden',297horizontal: 'hidden',298handleMouseWheel: false,299useShadows: false,300},301overviewRulerLanes: 0,302fixedOverflowWidgets: true,303overviewRulerBorder: false,304stickyScroll: { enabled: false },305minimap: { enabled: false },306readOnly: true,307automaticLayout: false,308};309310const makeFrameElements = () => dom.h('div.multiCallStackFrame', [311dom.h('div.header@header', [312dom.h('div.collapse-button@collapseButton'),313dom.h('div.title.show-file-icons@title'),314dom.h('div.actions@actions'),315]),316317dom.h('div.editorParent', [318dom.h('div.editorContainer@editor'),319])320]);321322export const CALL_STACK_WIDGET_HEADER_HEIGHT = 24;323324interface IAbstractFrameRendererTemplateData {325container: HTMLElement;326label: ResourceLabel;327elements: ReturnType<typeof makeFrameElements>;328decorations: string[];329collapse: Button;330elementStore: DisposableStore;331templateStore: DisposableStore;332}333334abstract class AbstractFrameRenderer<T extends IAbstractFrameRendererTemplateData> implements IListRenderer<ListItem, T> {335public abstract templateId: string;336337constructor(338@IInstantiationService protected readonly instantiationService: IInstantiationService,339) { }340341renderTemplate(container: HTMLElement): T {342const elements = makeFrameElements();343container.appendChild(elements.root);344345346const templateStore = new DisposableStore();347container.classList.add('multiCallStackFrameContainer');348templateStore.add(toDisposable(() => {349container.classList.remove('multiCallStackFrameContainer');350elements.root.remove();351}));352353const label = templateStore.add(this.instantiationService.createInstance(ResourceLabel, elements.title, {}));354355const collapse = templateStore.add(new Button(elements.collapseButton, {}));356357const contentId = generateUuid();358elements.editor.id = contentId;359elements.editor.role = 'region';360elements.collapseButton.setAttribute('aria-controls', contentId);361362return this.finishRenderTemplate({363container,364decorations: [],365elements,366label,367collapse,368elementStore: templateStore.add(new DisposableStore()),369templateStore,370});371}372373protected abstract finishRenderTemplate(data: IAbstractFrameRendererTemplateData): T;374375renderElement(element: ListItem, index: number, template: T): void {376const { elementStore } = template;377elementStore.clear();378const item = element as IFrameLikeItem;379380this.setupCollapseButton(item, template);381}382383private setupCollapseButton(item: IFrameLikeItem, { elementStore, elements, collapse }: T) {384elementStore.add(autorun(reader => {385collapse.element.className = '';386const collapsed = item.collapsed.read(reader);387collapse.icon = collapsed ? Codicon.chevronRight : Codicon.chevronDown;388collapse.element.ariaExpanded = String(!collapsed);389elements.root.classList.toggle('collapsed', collapsed);390}));391const toggleCollapse = () => item.collapsed.set(!item.collapsed.get(), undefined);392elementStore.add(collapse.onDidClick(toggleCollapse));393elementStore.add(dom.addDisposableListener(elements.title, 'click', toggleCollapse));394}395396disposeElement(element: ListItem, index: number, templateData: T): void {397templateData.elementStore.clear();398}399400disposeTemplate(templateData: T): void {401templateData.templateStore.dispose();402}403}404405const CONTEXT_LINES = 2;406407/** Renderer for a normal stack frame where code is available. */408class FrameCodeRenderer extends AbstractFrameRenderer<IStackTemplateData> {409public static readonly templateId = 'f';410411public readonly templateId = FrameCodeRenderer.templateId;412413constructor(414private readonly containingEditor: ICodeEditor | undefined,415private readonly onLayout: Event<void>,416@ITextModelService private readonly modelService: ITextModelService,417@IInstantiationService instantiationService: IInstantiationService,418) {419super(instantiationService);420}421422protected override finishRenderTemplate(data: IAbstractFrameRendererTemplateData): IStackTemplateData {423// override default e.g. language contributions, only allow users to click424// on code in the call stack to go to its source location425const contributions: IEditorContributionDescription[] = [{426id: ClickToLocationContribution.ID,427instantiation: EditorContributionInstantiation.BeforeFirstInteraction,428ctor: ClickToLocationContribution as EditorContributionCtor,429}];430431const editor = this.containingEditor432? this.instantiationService.createInstance(433EmbeddedCodeEditorWidget,434data.elements.editor,435editorOptions,436{ isSimpleWidget: true, contributions },437this.containingEditor,438)439: this.instantiationService.createInstance(440CodeEditorWidget,441data.elements.editor,442editorOptions,443{ isSimpleWidget: true, contributions },444);445446data.templateStore.add(editor);447448const toolbar = data.templateStore.add(this.instantiationService.createInstance(MenuWorkbenchToolBar, data.elements.actions, MenuId.DebugCallStackToolbar, {449menuOptions: { shouldForwardArgs: true },450actionViewItemProvider: (action, options) => createActionViewItem(this.instantiationService, action, options),451}));452453return { ...data, editor, toolbar };454}455456override renderElement(element: ListItem, index: number, template: IStackTemplateData): void {457super.renderElement(element, index, template);458459const { elementStore, editor } = template;460461const item = element as WrappedCallStackFrame;462const uri = item.source!;463464template.label.element.setFile(uri);465const cts = new CancellationTokenSource();466elementStore.add(toDisposable(() => cts.dispose(true)));467this.modelService.createModelReference(uri).then(reference => {468if (cts.token.isCancellationRequested) {469return reference.dispose();470}471472elementStore.add(reference);473editor.setModel(reference.object.textEditorModel);474this.setupEditorAfterModel(item, template);475this.setupEditorLayout(item, template);476});477}478479private setupEditorLayout(item: WrappedCallStackFrame, { elementStore, container, editor }: IStackTemplateData) {480const layout = () => {481const prev = editor.getContentHeight();482editor.layout({ width: container.clientWidth, height: prev });483484const next = editor.getContentHeight();485if (next !== prev) {486editor.layout({ width: container.clientWidth, height: next });487}488489item.editorHeight.set(next, undefined);490};491elementStore.add(editor.onDidChangeModelDecorations(layout));492elementStore.add(editor.onDidChangeModelContent(layout));493elementStore.add(editor.onDidChangeModelOptions(layout));494elementStore.add(this.onLayout(layout));495layout();496}497498private setupEditorAfterModel(item: WrappedCallStackFrame, template: IStackTemplateData): void {499const range = Range.fromPositions({500column: item.column ?? 1,501lineNumber: item.line ?? 1,502});503504template.toolbar.context = { uri: item.source, range };505506template.editor.setHiddenAreas([507Range.fromPositions(508{ column: 1, lineNumber: 1 },509{ column: 1, lineNumber: Math.max(1, item.line - CONTEXT_LINES - 1) },510),511Range.fromPositions(512{ column: 1, lineNumber: item.line + CONTEXT_LINES + 1 },513{ column: 1, lineNumber: Constants.MAX_SAFE_SMALL_INTEGER },514),515]);516517template.editor.changeDecorations(accessor => {518for (const d of template.decorations) {519accessor.removeDecoration(d);520}521template.decorations.length = 0;522523const beforeRange = range.setStartPosition(range.startLineNumber, 1);524const hasCharactersBefore = !!template.editor.getModel()?.getValueInRange(beforeRange).trim();525const decoRange = range.setEndPosition(range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);526527template.decorations.push(accessor.addDecoration(528decoRange,529makeStackFrameColumnDecoration(!hasCharactersBefore),530));531template.decorations.push(accessor.addDecoration(532decoRange,533TOP_STACK_FRAME_DECORATION,534));535});536537item.editorHeight.set(template.editor.getContentHeight(), undefined);538}539}540541interface IMissingTemplateData {542elements: ReturnType<typeof makeFrameElements>;543label: ResourceLabel;544}545546/** Renderer for a call frame that's missing a URI */547class MissingCodeRenderer implements IListRenderer<ListItem, IMissingTemplateData> {548public static readonly templateId = 'm';549public readonly templateId = MissingCodeRenderer.templateId;550551constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { }552553renderTemplate(container: HTMLElement): IMissingTemplateData {554const elements = makeFrameElements();555elements.root.classList.add('missing');556container.appendChild(elements.root);557const label = this.instantiationService.createInstance(ResourceLabel, elements.title, {});558return { elements, label };559}560561renderElement(element: ListItem, _index: number, templateData: IMissingTemplateData): void {562const cast = element as CallStackFrame;563templateData.label.element.setResource({564name: cast.name,565description: localize('stackFrameLocation', 'Line {0} column {1}', cast.line, cast.column),566range: { startLineNumber: cast.line, startColumn: cast.column, endColumn: cast.column, endLineNumber: cast.line },567}, {568icon: Codicon.fileBinary,569});570}571572disposeTemplate(templateData: IMissingTemplateData): void {573templateData.label.dispose();574templateData.elements.root.remove();575}576}577578/** Renderer for a call frame that's missing a URI */579class CustomRenderer extends AbstractFrameRenderer<IAbstractFrameRendererTemplateData> {580public static readonly templateId = 'c';581public readonly templateId = CustomRenderer.templateId;582583protected override finishRenderTemplate(data: IAbstractFrameRendererTemplateData): IAbstractFrameRendererTemplateData {584return data;585}586587override renderElement(element: ListItem, index: number, template: IAbstractFrameRendererTemplateData): void {588super.renderElement(element, index, template);589590const item = element as WrappedCustomStackFrame;591const { elementStore, container, label } = template;592593label.element.setResource({ name: item.original.label }, { icon: item.original.icon });594595elementStore.add(autorun(reader => {596template.elements.header.style.display = item.original.showHeader.read(reader) ? '' : 'none';597}));598599elementStore.add(autorunWithStore((reader, store) => {600if (!item.collapsed.read(reader)) {601store.add(item.original.render(container));602}603}));604605const actions = item.original.renderActions?.(template.elements.actions);606if (actions) {607elementStore.add(actions);608}609}610}611612interface ISkippedTemplateData {613button: Button;614current?: SkippedCallFrames;615store: DisposableStore;616}617618/** Renderer for a button to load more call frames */619class SkippedRenderer implements IListRenderer<ListItem, ISkippedTemplateData> {620public static readonly templateId = 's';621public readonly templateId = SkippedRenderer.templateId;622623constructor(624private readonly loadFrames: (fromItem: SkippedCallFrames) => Promise<void>,625@INotificationService private readonly notificationService: INotificationService,626) { }627628renderTemplate(container: HTMLElement): ISkippedTemplateData {629const store = new DisposableStore();630const button = new Button(container, { title: '', ...defaultButtonStyles });631const data: ISkippedTemplateData = { button, store };632633store.add(button);634store.add(button.onDidClick(() => {635if (!data.current || !button.enabled) {636return;637}638639button.enabled = false;640this.loadFrames(data.current).catch(e => {641this.notificationService.error(localize('failedToLoadFrames', 'Failed to load stack frames: {0}', e.message));642});643}));644645return data;646}647648renderElement(element: ListItem, index: number, templateData: ISkippedTemplateData): void {649const cast = element as SkippedCallFrames;650templateData.button.enabled = true;651templateData.button.label = cast.label;652templateData.current = cast;653}654655disposeTemplate(templateData: ISkippedTemplateData): void {656templateData.store.dispose();657}658}659660/** A simple contribution that makes all data in the editor clickable to go to the location */661class ClickToLocationContribution extends Disposable implements IEditorContribution {662public static readonly ID = 'clickToLocation';663private readonly linkDecorations: IEditorDecorationsCollection;664private current: { line: number; word: IWordAtPosition } | undefined;665666constructor(667private readonly editor: ICodeEditor,668@IEditorService editorService: IEditorService,669) {670super();671this.linkDecorations = editor.createDecorationsCollection();672this._register(toDisposable(() => this.linkDecorations.clear()));673674const clickLinkGesture = this._register(new ClickLinkGesture(editor));675676this._register(clickLinkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => {677this.onMove(mouseEvent);678}));679this._register(clickLinkGesture.onExecute((e) => {680const model = this.editor.getModel();681if (!this.current || !model) {682return;683}684685editorService.openEditor({686resource: model.uri,687options: {688selection: Range.fromPositions(new Position(this.current.line, this.current.word.startColumn)),689selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport,690},691}, e.hasSideBySideModifier ? SIDE_GROUP : undefined);692}));693}694695private onMove(mouseEvent: ClickLinkMouseEvent) {696if (!mouseEvent.hasTriggerModifier) {697return this.clear();698}699700const position = mouseEvent.target.position;701const word = position && this.editor.getModel()?.getWordAtPosition(position);702if (!word) {703return this.clear();704}705706const prev = this.current?.word;707if (prev && prev.startColumn === word.startColumn && prev.endColumn === word.endColumn && prev.word === word.word) {708return;709}710711this.current = { word, line: position.lineNumber };712this.linkDecorations.set([{713range: new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn),714options: {715description: 'call-stack-go-to-file-link',716inlineClassName: 'call-stack-go-to-file-link',717},718}]);719}720721private clear() {722this.linkDecorations.clear();723this.current = undefined;724}725}726727registerAction2(class extends Action2 {728constructor() {729super({730id: 'callStackWidget.goToFile',731title: localize2('goToFile', 'Open File'),732icon: Codicon.goToFile,733menu: {734id: MenuId.DebugCallStackToolbar,735order: 22,736group: 'navigation',737},738});739}740741async run(accessor: ServicesAccessor, { uri, range }: Location): Promise<void> {742const editorService = accessor.get(IEditorService);743await editorService.openEditor({744resource: uri,745options: {746selection: range,747selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport,748},749});750}751});752753754