UNPKG

monaco-editor-core

Version:

A browser based code editor

239 lines (238 loc) • 9.3 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from '../../../../base/common/errors.js'; import { Emitter } from '../../../../base/common/event.js'; import { defaultGenerator } from '../../../../base/common/idGenerator.js'; import { dispose } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { basename, extUri } from '../../../../base/common/resources.js'; import * as strings from '../../../../base/common/strings.js'; import { Range } from '../../../common/core/range.js'; import { localize } from '../../../../nls.js'; export class OneReference { constructor(isProviderFirst, parent, link, _rangeCallback) { this.isProviderFirst = isProviderFirst; this.parent = parent; this.link = link; this._rangeCallback = _rangeCallback; this.id = defaultGenerator.nextId(); } get uri() { return this.link.uri; } get range() { return this._range ?? this.link.targetSelectionRange ?? this.link.range; } set range(value) { this._range = value; this._rangeCallback(this); } get ariaMessage() { const preview = this.parent.getPreview(this)?.preview(this.range); if (!preview) { return localize('aria.oneReference', "in {0} on line {1} at column {2}", basename(this.uri), this.range.startLineNumber, this.range.startColumn); } else { return localize({ key: 'aria.oneReference.preview', comment: ['Placeholders are: 0: filename, 1:line number, 2: column number, 3: preview snippet of source code'] }, "{0} in {1} on line {2} at column {3}", preview.value, basename(this.uri), this.range.startLineNumber, this.range.startColumn); } } } export class FilePreview { constructor(_modelReference) { this._modelReference = _modelReference; } dispose() { this._modelReference.dispose(); } preview(range, n = 8) { const model = this._modelReference.object.textEditorModel; if (!model) { return undefined; } const { startLineNumber, startColumn, endLineNumber, endColumn } = range; const word = model.getWordUntilPosition({ lineNumber: startLineNumber, column: startColumn - n }); const beforeRange = new Range(startLineNumber, word.startColumn, startLineNumber, startColumn); const afterRange = new Range(endLineNumber, endColumn, endLineNumber, 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */); const before = model.getValueInRange(beforeRange).replace(/^\s+/, ''); const inside = model.getValueInRange(range); const after = model.getValueInRange(afterRange).replace(/\s+$/, ''); return { value: before + inside + after, highlight: { start: before.length, end: before.length + inside.length } }; } } export class FileReferences { constructor(parent, uri) { this.parent = parent; this.uri = uri; this.children = []; this._previews = new ResourceMap(); } dispose() { dispose(this._previews.values()); this._previews.clear(); } getPreview(child) { return this._previews.get(child.uri); } get ariaMessage() { const len = this.children.length; if (len === 1) { return localize('aria.fileReferences.1', "1 symbol in {0}, full path {1}", basename(this.uri), this.uri.fsPath); } else { return localize('aria.fileReferences.N', "{0} symbols in {1}, full path {2}", len, basename(this.uri), this.uri.fsPath); } } async resolve(textModelResolverService) { if (this._previews.size !== 0) { return this; } for (const child of this.children) { if (this._previews.has(child.uri)) { continue; } try { const ref = await textModelResolverService.createModelReference(child.uri); this._previews.set(child.uri, new FilePreview(ref)); } catch (err) { onUnexpectedError(err); } } return this; } } export class ReferencesModel { constructor(links, title) { this.groups = []; this.references = []; this._onDidChangeReferenceRange = new Emitter(); this.onDidChangeReferenceRange = this._onDidChangeReferenceRange.event; this._links = links; this._title = title; // grouping and sorting const [providersFirst] = links; links.sort(ReferencesModel._compareReferences); let current; for (const link of links) { if (!current || !extUri.isEqual(current.uri, link.uri, true)) { // new group current = new FileReferences(this, link.uri); this.groups.push(current); } // append, check for equality first! if (current.children.length === 0 || ReferencesModel._compareReferences(link, current.children[current.children.length - 1]) !== 0) { const oneRef = new OneReference(providersFirst === link, current, link, ref => this._onDidChangeReferenceRange.fire(ref)); this.references.push(oneRef); current.children.push(oneRef); } } } dispose() { dispose(this.groups); this._onDidChangeReferenceRange.dispose(); this.groups.length = 0; } clone() { return new ReferencesModel(this._links, this._title); } get title() { return this._title; } get isEmpty() { return this.groups.length === 0; } get ariaMessage() { if (this.isEmpty) { return localize('aria.result.0', "No results found"); } else if (this.references.length === 1) { return localize('aria.result.1', "Found 1 symbol in {0}", this.references[0].uri.fsPath); } else if (this.groups.length === 1) { return localize('aria.result.n1', "Found {0} symbols in {1}", this.references.length, this.groups[0].uri.fsPath); } else { return localize('aria.result.nm', "Found {0} symbols in {1} files", this.references.length, this.groups.length); } } nextOrPreviousReference(reference, next) { const { parent } = reference; let idx = parent.children.indexOf(reference); const childCount = parent.children.length; const groupCount = parent.parent.groups.length; if (groupCount === 1 || next && idx + 1 < childCount || !next && idx > 0) { // cycling within one file if (next) { idx = (idx + 1) % childCount; } else { idx = (idx + childCount - 1) % childCount; } return parent.children[idx]; } idx = parent.parent.groups.indexOf(parent); if (next) { idx = (idx + 1) % groupCount; return parent.parent.groups[idx].children[0]; } else { idx = (idx + groupCount - 1) % groupCount; return parent.parent.groups[idx].children[parent.parent.groups[idx].children.length - 1]; } } nearestReference(resource, position) { const nearest = this.references.map((ref, idx) => { return { idx, prefixLen: strings.commonPrefixLength(ref.uri.toString(), resource.toString()), offsetDist: Math.abs(ref.range.startLineNumber - position.lineNumber) * 100 + Math.abs(ref.range.startColumn - position.column) }; }).sort((a, b) => { if (a.prefixLen > b.prefixLen) { return -1; } else if (a.prefixLen < b.prefixLen) { return 1; } else if (a.offsetDist < b.offsetDist) { return -1; } else if (a.offsetDist > b.offsetDist) { return 1; } else { return 0; } })[0]; if (nearest) { return this.references[nearest.idx]; } return undefined; } referenceAt(resource, position) { for (const ref of this.references) { if (ref.uri.toString() === resource.toString()) { if (Range.containsPosition(ref.range, position)) { return ref; } } } return undefined; } firstReference() { for (const ref of this.references) { if (ref.isProviderFirst) { return ref; } } return this.references[0]; } static _compareReferences(a, b) { return extUri.compare(a.uri, b.uri) || Range.compareRangesUsingStarts(a.range, b.range); } }