Path: blob/main/src/vs/editor/contrib/sectionHeaders/browser/sectionHeaders.ts
3296 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 { CancelablePromise, RunOnceScheduler } from '../../../../base/common/async.js';6import { Disposable } from '../../../../base/common/lifecycle.js';7import { ICodeEditor } from '../../../browser/editorBrowser.js';8import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js';9import { EditorOption, IEditorMinimapOptions } from '../../../common/config/editorOptions.js';10import { IEditorContribution, IEditorDecorationsCollection } from '../../../common/editorCommon.js';11import { StandardTokenType } from '../../../common/encodedTokenAttributes.js';12import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';13import { IModelDeltaDecoration, MinimapPosition, MinimapSectionHeaderStyle, TrackedRangeStickiness } from '../../../common/model.js';14import { ModelDecorationOptions } from '../../../common/model/textModel.js';15import { IEditorWorkerService } from '../../../common/services/editorWorker.js';16import { FindSectionHeaderOptions, SectionHeader } from '../../../common/services/findSectionHeaders.js';1718export class SectionHeaderDetector extends Disposable implements IEditorContribution {1920public static readonly ID: string = 'editor.sectionHeaderDetector';2122private options: FindSectionHeaderOptions | undefined;23private decorations: IEditorDecorationsCollection;24private computeSectionHeaders: RunOnceScheduler;25private computePromise: CancelablePromise<SectionHeader[]> | null;26private currentOccurrences: { [decorationId: string]: SectionHeaderOccurrence };2728constructor(29private readonly editor: ICodeEditor,30@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,31@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService,32) {33super();34this.decorations = this.editor.createDecorationsCollection();3536this.options = this.createOptions(editor.getOption(EditorOption.minimap));37this.computePromise = null;38this.currentOccurrences = {};3940this._register(editor.onDidChangeModel((e) => {41this.currentOccurrences = {};42this.options = this.createOptions(editor.getOption(EditorOption.minimap));43this.stop();44this.computeSectionHeaders.schedule(0);45}));4647this._register(editor.onDidChangeModelLanguage((e) => {48this.currentOccurrences = {};49this.options = this.createOptions(editor.getOption(EditorOption.minimap));50this.stop();51this.computeSectionHeaders.schedule(0);52}));5354this._register(languageConfigurationService.onDidChange((e) => {55const editorLanguageId = this.editor.getModel()?.getLanguageId();56if (editorLanguageId && e.affects(editorLanguageId)) {57this.currentOccurrences = {};58this.options = this.createOptions(editor.getOption(EditorOption.minimap));59this.stop();60this.computeSectionHeaders.schedule(0);61}62}));6364this._register(editor.onDidChangeConfiguration(e => {65if (this.options && !e.hasChanged(EditorOption.minimap)) {66return;67}6869this.options = this.createOptions(editor.getOption(EditorOption.minimap));7071// Remove any links (for the getting disabled case)72this.updateDecorations([]);7374// Stop any computation (for the getting disabled case)75this.stop();7677// Start computing (for the getting enabled case)78this.computeSectionHeaders.schedule(0);79}));8081this._register(this.editor.onDidChangeModelContent(e => {82this.computeSectionHeaders.schedule();83}));8485this._register(editor.onDidChangeModelTokens((e) => {86if (!this.computeSectionHeaders.isScheduled()) {87this.computeSectionHeaders.schedule(1000);88}89}));9091this.computeSectionHeaders = this._register(new RunOnceScheduler(() => {92this.findSectionHeaders();93}, 250));9495this.computeSectionHeaders.schedule(0);96}9798private createOptions(minimap: Readonly<Required<IEditorMinimapOptions>>): FindSectionHeaderOptions | undefined {99if (!minimap || !this.editor.hasModel()) {100return undefined;101}102103const languageId = this.editor.getModel().getLanguageId();104if (!languageId) {105return undefined;106}107108const commentsConfiguration = this.languageConfigurationService.getLanguageConfiguration(languageId).comments;109const foldingRules = this.languageConfigurationService.getLanguageConfiguration(languageId).foldingRules;110111if (!commentsConfiguration && !foldingRules?.markers) {112return undefined;113}114115return {116foldingRules,117markSectionHeaderRegex: minimap.markSectionHeaderRegex,118findMarkSectionHeaders: minimap.showMarkSectionHeaders,119findRegionSectionHeaders: minimap.showRegionSectionHeaders,120};121}122123private findSectionHeaders() {124if (!this.editor.hasModel()125|| (!this.options?.findMarkSectionHeaders && !this.options?.findRegionSectionHeaders)) {126return;127}128129const model = this.editor.getModel();130if (model.isDisposed() || model.isTooLargeForSyncing()) {131return;132}133134const modelVersionId = model.getVersionId();135this.editorWorkerService.findSectionHeaders(model.uri, this.options)136.then((sectionHeaders) => {137if (model.isDisposed() || model.getVersionId() !== modelVersionId) {138// model changed in the meantime139return;140}141this.updateDecorations(sectionHeaders);142});143}144145private updateDecorations(sectionHeaders: SectionHeader[]): void {146147const model = this.editor.getModel();148if (model) {149// Remove all section headers that should be in comments and are not in comments150sectionHeaders = sectionHeaders.filter((sectionHeader) => {151if (!sectionHeader.shouldBeInComments) {152return true;153}154const validRange = model.validateRange(sectionHeader.range);155const tokens = model.tokenization.getLineTokens(validRange.startLineNumber);156const idx = tokens.findTokenIndexAtOffset(validRange.startColumn - 1);157const tokenType = tokens.getStandardTokenType(idx);158const languageId = tokens.getLanguageId(idx);159return (languageId === model.getLanguageId() && tokenType === StandardTokenType.Comment);160});161}162163const oldDecorations = Object.values(this.currentOccurrences).map(occurrence => occurrence.decorationId);164const newDecorations = sectionHeaders.map(sectionHeader => decoration(sectionHeader));165166this.editor.changeDecorations((changeAccessor) => {167const decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations);168169this.currentOccurrences = {};170for (let i = 0, len = decorations.length; i < len; i++) {171const occurrence = { sectionHeader: sectionHeaders[i], decorationId: decorations[i] };172this.currentOccurrences[occurrence.decorationId] = occurrence;173}174});175}176177private stop(): void {178this.computeSectionHeaders.cancel();179if (this.computePromise) {180this.computePromise.cancel();181this.computePromise = null;182}183}184185public override dispose(): void {186super.dispose();187this.stop();188this.decorations.clear();189}190191}192193interface SectionHeaderOccurrence {194readonly sectionHeader: SectionHeader;195readonly decorationId: string;196}197198function decoration(sectionHeader: SectionHeader): IModelDeltaDecoration {199return {200range: sectionHeader.range,201options: ModelDecorationOptions.createDynamic({202description: 'section-header',203stickiness: TrackedRangeStickiness.GrowsOnlyWhenTypingAfter,204collapseOnReplaceEdit: true,205minimap: {206color: undefined,207position: MinimapPosition.Inline,208sectionHeaderStyle: sectionHeader.hasSeparatorLine ? MinimapSectionHeaderStyle.Underlined : MinimapSectionHeaderStyle.Normal,209sectionHeaderText: sectionHeader.text,210},211})212};213}214215registerEditorContribution(SectionHeaderDetector.ID, SectionHeaderDetector, EditorContributionInstantiation.AfterFirstRender);216217218