This commit is contained in:
Mathew Pareles 2025-02-03 18:27:57 -08:00
parent 41057c20fc
commit 145fd9614b
5 changed files with 626 additions and 400 deletions

View file

@ -54,7 +54,8 @@ export const sendOllamaFIM: _InternalOllamaFIMMessageFnType = ({ messages, onTex
suffix: messages.suffix,
options: {
stop: messages.stopTokens,
num_predict: 300 // max tokens
num_predict: 300, // max tokens
// repeat_penalty: 1,
},
raw: true,
stream: true,

View file

@ -8,7 +8,7 @@ import { ILanguageFeaturesService } from '../../../../editor/common/services/lan
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { Position } from '../../../../editor/common/core/position.js';
import { DocumentSymbol, InlineCompletion, InlineCompletionContext, Location, } from '../../../../editor/common/languages.js';
import { InlineCompletion, InlineCompletionContext, } from '../../../../editor/common/languages.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Range } from '../../../../editor/common/core/range.js';
import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
@ -19,6 +19,7 @@ import { IModelService } from '../../../../editor/common/services/model.js';
import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
import { isWindows } from '../../../../base/common/platform.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { IContextGatheringService } from './contextGatheringService.js';
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts
@ -746,11 +747,12 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
// gather relevant context from the code around the user's selection and definitions
const relevantContext = await this._gatherRelevantContextForPosition(model, position);
// const relevantSnippetsList = await this._contextGatheringService.readCachedSnippets(model, position, 3);
const relevantSnippetsList = this._contextGatheringService.getCachedSnippets();
const relevantSnippets = relevantSnippetsList.map((text) => `${text}`).join('\n-------------------------------\n')
console.log('@@---------------------\n' + relevantSnippets)
console.log('@@---------------------\n' + relevantContext)
const { shouldGenerate, predictionType, llmPrefix, llmSuffix, stopTokens } = getCompletionOptions(prefixAndSuffix, relevantContext, justAcceptedAutocompletion)
const { shouldGenerate, predictionType, llmPrefix, llmSuffix, stopTokens } = getCompletionOptions(prefixAndSuffix, relevantSnippets, justAcceptedAutocompletion)
if (!shouldGenerate) return []
@ -870,405 +872,12 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
}
// TODO! Given a user's cursor position, get relevant context.
// algorithm pseudocode:
// 1. get all relevant symbols (functions, variables, and types)
// 1a. get all symbols that are `numNearbyLines` lines above and below the current position
// eg. if the context is this:
// ```
// ...
// const addVectors = (a: Vector, b: Vector) => {
//
// ... 100+ LINES OF CODE
// return addVectorsElementWise(a,b, Math.min(a.length, b.length) as NumberType) [[CURSOR]]
// }
// ...
// ```
// then these are all of the symbols it should consider that are above and below the position: ['addVectorsElementWise', 'Math.min', 'a.length', 'b.length', 'NumberType']
// 1b. look at where the parent function is defined and get its nearby symbols `numParentLines`
// ex.
// ```
// ...
// const addVectors = (a: Vector, b: Vector) => { [[THIS IS THE PARENT FUNCTION]]
// ... 100+ LINES OF CODE
// return addVectorsElementWise(a ,b, Math.min(a.length, b.length)) [[CURSOR IS HERE]]
// }
// ...
// ```
// the symbols of the parent function are ['const', 'addVectors', 'a', 'Vector', 'b', 'Vector']
// 2. Cmd+Click on each symbol in step 1. (view instances and definitions)
// check that you don't visit the same place twice
// if this location is new, get `` lines above and below this new location and save that string to an array
// 3. for each of the new positions found in step 2., use step 1 to find all their symbols again. This is the recursive step.
// use `maxRecursionDepth` to prevent slowness
// set `numNearbyLines` and `numParentLines` to 2 after the first step to increase performance
// 4. when finished, return snippets.join('\n----------------\n')
private _docSymbolsCache: {
[docUri: string]: {
version: number;
symbols: DocumentSymbol[];
};
} = Object.create(null);
// For each file, store per-symbol lookups we've done.
// e.g. _symbolLookupCache[docUri][fileVersion]["root"] => Location[] results
private _symbolLookupCache: {
[docUri: string]: {
[version: number]: {
[symbolName: string]: Location[];
};
};
} = Object.create(null);
private async _gatherRelevantContextForPosition(
model: ITextModel,
position: Position,
maxRecursionDepth: number = 3,
numNearbyLines: number = 5,
numParentLines: number = 5,
numSaveLines: number = 10
): Promise<string> {
/****************************************************************************
* A. Quick Helpers & caches
****************************************************************************/
type EditorLocation = import('vs/editor/common/languages').Location;
const docUri = model.uri.toString();
const fileVersion = model.getAlternativeVersionId();
// If you prefer, do a text-based hash or use model.getVersionId() instead.
// 1) Ensure docSymbols cache
let docSymCache = this._docSymbolsCache[docUri];
if (!docSymCache || docSymCache.version !== fileVersion) {
docSymCache = {
version: fileVersion,
symbols: await this._getDocumentSymbolsOnce(model) // see helper below
};
this._docSymbolsCache[docUri] = docSymCache;
}
const allDocumentSymbols = docSymCache.symbols;
// 2) Ensure symbol lookup cache
if (!this._symbolLookupCache[docUri]) {
this._symbolLookupCache[docUri] = {};
}
if (!this._symbolLookupCache[docUri][fileVersion]) {
this._symbolLookupCache[docUri][fileVersion] = {};
}
const symbolLookupForFile = this._symbolLookupCache[docUri][fileVersion];
// Basic numeric clamps
const clampLine = (line: number): number => {
const maxLine = model.getLineCount();
return Math.max(1, Math.min(line, maxLine));
};
// Return a snippet of lines [start..end] in the document
const snippetForRange = (startLine: number, endLine: number): string => {
const lines: string[] = [];
for (let ln = startLine; ln <= endLine; ln++) {
lines.push(model.getLineContent(ln));
}
return lines.join('\n');
};
/****************************************************************************
* B. Interval-based BFS to gather code blocks without duplication
****************************************************************************/
interface Interval { start: number; end: number; }
function addInterval(intervals: Interval[], start: number, end: number) {
// Merge new [start..end] with existing intervals if they overlap or touch
for (let i = 0; i < intervals.length; i++) {
const iv = intervals[i];
if (!(end < iv.start - 1 || start > iv.end + 1)) {
// Overlaps (or touches); merge
const mergedStart = Math.min(iv.start, start);
const mergedEnd = Math.max(iv.end, end);
intervals.splice(i, 1); // remove old
addInterval(intervals, mergedStart, mergedEnd); // re-run
return;
}
}
intervals.push({ start, end });
}
function intervalsToString(intervals: Interval[]): string {
intervals.sort((a, b) => a.start - b.start);
return intervals
.map(iv => snippetForRange(iv.start, iv.end))
.join('\n------------------------------\n');
}
const intervals: Interval[] = [];
const visitedRanges = new Set<string>();
function markVisited(s: number, e: number) { visitedRanges.add(`${s}-${e}`); }
function isVisited(s: number, e: number) { return visitedRanges.has(`${s}-${e}`); }
/****************************************************************************
* C. Compute initial intervals (cursor region, parent symbol region)
****************************************************************************/
const lineNumber = position.lineNumber;
const localStart = clampLine(lineNumber - numNearbyLines);
const localEnd = clampLine(lineNumber + numNearbyLines);
addInterval(intervals, clampLine(localStart - numSaveLines), clampLine(localEnd + numSaveLines));
markVisited(localStart, localEnd);
// get parent symbol, add interval for it
const parent = this._findEnclosingSymbol(allDocumentSymbols, lineNumber);
if (parent) {
const pStart = clampLine(parent.range.startLineNumber - numParentLines);
const pEnd = clampLine(parent.range.endLineNumber + numParentLines);
addInterval(intervals, pStart, pEnd);
markVisited(pStart, pEnd);
}
/****************************************************************************
* D. BFS data structures
****************************************************************************/
interface QItem { start: number; end: number; depth: number; }
const queue: QItem[] = [];
queue.push({ start: localStart, end: localEnd, depth: 1 });
if (parent) {
const pStart = clampLine(parent.range.startLineNumber - numParentLines);
const pEnd = clampLine(parent.range.endLineNumber + numParentLines);
queue.push({ start: pStart, end: pEnd, depth: 1 });
}
// We'll keep a set of symbols we've done "references + definitions" for:
const visitedSymbolNames = new Set<string>();
// Providers
const definitionProviders = this._langFeatureService.definitionProvider.ordered(model);
const referenceProviders = this._langFeatureService.referenceProvider.ordered(model);
/****************************************************************************
* E. BFS Loop
****************************************************************************/
while (queue.length) {
const { start, end, depth } = queue.shift()!;
if (depth >= maxRecursionDepth) continue;
// Step 1: Gather all symbols in [start..end]
const regionSyms = this._gatherSymbolsInLineRange(allDocumentSymbols, start, end);
// For each symbol, do references/defs once per symbol name
for (const sym of regionSyms) {
// If we already resolved that symbolName, skip
const symName = sym.name || '';
if (!symName) continue;
if (visitedSymbolNames.has(symName)) continue;
visitedSymbolNames.add(symName);
// If symbol was cached before, skip re-resolving references
if (symbolLookupForFile[symName]) {
// We already have references/definitions => merge them into intervals
const existingLocs = symbolLookupForFile[symName];
for (const loc of existingLocs) {
const rng = loc.range;
const locStart = clampLine(rng.startLineNumber - numSaveLines);
const locEnd = clampLine(rng.endLineNumber + numSaveLines);
if (!isVisited(locStart, locEnd)) {
markVisited(locStart, locEnd);
addInterval(intervals, locStart, locEnd);
queue.push({ start: locStart, end: locEnd, depth: depth + 1 });
}
}
continue;
}
// Not cached => actually ask definitionProviders / referenceProviders
const symPos = this._symbolPosition(sym); // see helper below
let foundLocs: EditorLocation[] = [];
for (const dp of definitionProviders) {
try {
const defs = await dp.provideDefinition(model, symPos, CancellationToken.None);
if (defs) foundLocs.push(...(Array.isArray(defs) ? defs : [defs]));
} catch {/* ignore */ }
}
for (const rp of referenceProviders) {
try {
const refs = await rp.provideReferences(
model, symPos, { includeDeclaration: true }, CancellationToken.None
);
if (refs) foundLocs.push(...refs);
} catch {/* ignore */ }
}
// Filter same-file only
foundLocs = foundLocs.filter(loc => loc.uri.toString() === docUri);
// Cache them
symbolLookupForFile[symName] = foundLocs;
// Enqueue each discovered reference/definition
for (const loc of foundLocs) {
const rng = loc.range;
const locStart = clampLine(rng.startLineNumber - numSaveLines);
const locEnd = clampLine(rng.endLineNumber + numSaveLines);
if (!isVisited(locStart, locEnd)) {
markVisited(locStart, locEnd);
addInterval(intervals, locStart, locEnd);
queue.push({ start: locStart, end: locEnd, depth: depth + 1 });
}
}
}
// Step 2: Also do naive token-scan for lines in [start..end],
// so e.g. 'root()' calls get recognized if not in docSymbols.
// We can do basically the same "cache symbol name" logic, if you want:
for (let ln = start; ln <= end; ln++) {
const text = model.getLineContent(ln);
const tokens = text.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || [];
for (const token of tokens) {
if (visitedSymbolNames.has(token)) continue;
visitedSymbolNames.add(token);
// If cached, merge intervals from cache
if (symbolLookupForFile[token]) {
for (const loc of symbolLookupForFile[token]) {
const rng = loc.range;
const locStart = clampLine(rng.startLineNumber - numSaveLines);
const locEnd = clampLine(rng.endLineNumber + numSaveLines);
if (!isVisited(locStart, locEnd)) {
markVisited(locStart, locEnd);
addInterval(intervals, locStart, locEnd);
queue.push({ start: locStart, end: locEnd, depth: depth + 1 });
}
}
continue;
}
// Actually compute definitions/references
const colIdx = text.indexOf(token);
if (colIdx < 0) continue; // should not happen, but just in case
const tokenPos = new Position(ln, colIdx + 1);
let foundLocs: EditorLocation[] = [];
for (const dp of definitionProviders) {
try {
const defs = await dp.provideDefinition(model, tokenPos, CancellationToken.None);
if (defs) foundLocs.push(...(Array.isArray(defs) ? defs : [defs]));
} catch {/* ignore */ }
}
for (const rp of referenceProviders) {
try {
const refs = await rp.provideReferences(
model, tokenPos, { includeDeclaration: true }, CancellationToken.None
);
if (refs) foundLocs.push(...refs);
} catch {/* ignore */ }
}
foundLocs = foundLocs.filter(loc => loc.uri.toString() === docUri);
// Cache them
symbolLookupForFile[token] = foundLocs;
// Add intervals
for (const loc of foundLocs) {
const rng = loc.range;
const locStart = clampLine(rng.startLineNumber - numSaveLines);
const locEnd = clampLine(rng.endLineNumber + numSaveLines);
if (!isVisited(locStart, locEnd)) {
markVisited(locStart, locEnd);
addInterval(intervals, locStart, locEnd);
queue.push({ start: locStart, end: locEnd, depth: depth + 1 });
}
}
}
}
}
/****************************************************************************
* F. Finally, merge intervals and produce final snippet
****************************************************************************/
return intervalsToString(intervals);
}
/******************************************************************************
* Additional Helpers
******************************************************************************/
private async _getDocumentSymbolsOnce(model: ITextModel): Promise<DocumentSymbol[]> {
const providers = this._langFeatureService.documentSymbolProvider.ordered(model);
let result: DocumentSymbol[] = [];
for (const p of providers) {
try {
const syms = await p.provideDocumentSymbols(model, CancellationToken.None);
if (syms) {
result.push(...syms);
}
} catch {/* ignore */ }
}
return result;
}
private _findEnclosingSymbol(symbols: DocumentSymbol[], line: number): DocumentSymbol | undefined {
for (const s of symbols) {
if (s.range.startLineNumber <= line && s.range.endLineNumber >= line) {
// Recurse deeper
const child = this._findEnclosingSymbol(s.children || [], line);
return child || s;
}
}
return undefined;
}
private _symbolPosition(ds: DocumentSymbol): Position {
return new Position(ds.selectionRange.startLineNumber, ds.selectionRange.startColumn);
}
private _gatherSymbolsInLineRange(
symbols: DocumentSymbol[],
startLine: number,
endLine: number
): DocumentSymbol[] {
const out: DocumentSymbol[] = [];
for (const ds of symbols) {
if (ds.range.endLineNumber >= startLine && ds.range.startLineNumber <= endLine) {
out.push(ds);
}
if (ds.children?.length) {
out.push(...this._gatherSymbolsInLineRange(ds.children, startLine, endLine));
}
}
return out;
}
constructor(
@ILanguageFeaturesService private _langFeatureService: ILanguageFeaturesService,
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
@IEditorService private readonly _editorService: IEditorService,
@IModelService private readonly _modelService: IModelService,
@IContextGatheringService private readonly _contextGatheringService: IContextGatheringService,
) {
super()

View file

@ -0,0 +1,354 @@
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Position } from '../../../../editor/common/core/position.js';
import { DocumentSymbol, SymbolKind } from '../../../../editor/common/languages.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { Range, IRange } from '../../../../editor/common/core/range.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { URI } from '../../../../base/common/uri.js';
// make sure snippet logic works
// change logic for `visited` to intervals
// atomically set new snippets at end
// throttle cache setting
interface IVisitedInterval {
uri: string;
startLine: number;
endLine: number;
}
export interface IContextGatheringService {
readonly _serviceBrand: undefined;
updateCache(model: ITextModel, pos: Position): Promise<void>;
getCachedSnippets(): string[];
}
export const IContextGatheringService = createDecorator<IContextGatheringService>('contextGatheringService');
class ContextGatheringService extends Disposable implements IContextGatheringService {
_serviceBrand: undefined;
private readonly _NUM_LINES = 3;
private readonly _MAX_SNIPPET_LINES = 7; // Reasonable size for context
// Cache holds the most recent list of snippets.
private _cache: string[] = [];
private _snippetIntervals: IVisitedInterval[] = [];
constructor(
@ILanguageFeaturesService private readonly _langFeaturesService: ILanguageFeaturesService,
@IModelService private readonly _modelService: IModelService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService
) {
super();
this._modelService.getModels().forEach(model => this._subscribeToModel(model));
this._register(this._modelService.onModelAdded(model => this._subscribeToModel(model)));
}
private _subscribeToModel(model: ITextModel): void {
console.log("Subscribing to model:", model.uri.toString());
this._register(model.onDidChangeContent(() => {
const editor = this._codeEditorService.getFocusedCodeEditor();
if (editor && editor.getModel() === model) {
const pos = editor.getPosition();
console.log("updateCache called at position:", pos);
if (pos) {
this.updateCache(model, pos);
}
}
}));
}
public async updateCache(model: ITextModel, pos: Position): Promise<void> {
const snippets = new Set<string>();
this._snippetIntervals = []; // Reset intervals for new cache update
await this._gatherNearbySnippets(model, pos, this._NUM_LINES, 3, snippets, this._snippetIntervals);
await this._gatherParentSnippets(model, pos, this._NUM_LINES, 3, snippets, this._snippetIntervals);
// Convert to array and filter overlapping snippets
this._cache = Array.from(snippets);
console.log("Cache updated:", this._cache);
}
public getCachedSnippets(): string[] {
return this._cache;
}
// Basic snippet extraction.
private _getSnippetForRange(model: ITextModel, range: IRange, numLines: number): string {
const startLine = Math.max(range.startLineNumber - numLines, 1);
const endLine = Math.min(range.endLineNumber + numLines, model.getLineCount());
// Enforce maximum snippet size
const totalLines = endLine - startLine + 1;
const adjustedStartLine = totalLines > this._MAX_SNIPPET_LINES
? endLine - this._MAX_SNIPPET_LINES + 1
: startLine;
const snippetRange = new Range(adjustedStartLine, 1, endLine, model.getLineMaxColumn(endLine));
return this._cleanSnippet(model.getValueInRange(snippetRange));
}
private _cleanSnippet(snippet: string): string {
return snippet
.split('\n')
// Remove empty lines and lines with only comments
.filter(line => {
const trimmed = line.trim();
return trimmed && !/^\/\/+$/.test(trimmed);
})
// Rejoin with newlines
.join('\n')
// Remove excess whitespace
.trim();
}
private _normalizeSnippet(snippet: string): string {
return snippet
// Remove multiple newlines
.replace(/\n{2,}/g, '\n')
// Remove trailing whitespace
.trim();
}
private _addSnippetIfNotOverlapping(
model: ITextModel,
range: IRange,
snippets: Set<string>,
visited: IVisitedInterval[]
): void {
const startLine = range.startLineNumber;
const endLine = range.endLineNumber;
const uri = model.uri.toString();
if (!this._isRangeVisited(uri, startLine, endLine, visited)) {
visited.push({ uri, startLine, endLine });
const snippet = this._normalizeSnippet(this._getSnippetForRange(model, range, this._NUM_LINES));
if (snippet.length > 0) {
snippets.add(snippet);
}
}
}
private async _gatherNearbySnippets(
model: ITextModel,
pos: Position,
numLines: number,
depth: number,
snippets: Set<string>,
visited: IVisitedInterval[]
): Promise<void> {
if (depth <= 0) return;
const startLine = Math.max(pos.lineNumber - numLines, 1);
const endLine = Math.min(pos.lineNumber + numLines, model.getLineCount());
const range = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine));
this._addSnippetIfNotOverlapping(model, range, snippets, visited);
const symbols = await this._getSymbolsNearPosition(model, pos, numLines);
for (const sym of symbols) {
const defs = await this._getDefinitionSymbols(model, sym);
for (const def of defs) {
const defModel = this._modelService.getModel(def.uri);
if (defModel) {
const defPos = new Position(def.range.startLineNumber, def.range.startColumn);
this._addSnippetIfNotOverlapping(defModel, def.range, snippets, visited);
await this._gatherNearbySnippets(defModel, defPos, numLines, depth - 1, snippets, visited);
}
}
}
}
private async _gatherParentSnippets(
model: ITextModel,
pos: Position,
numLines: number,
depth: number,
snippets: Set<string>,
visited: IVisitedInterval[]
): Promise<void> {
if (depth <= 0) return;
const container = await this._findContainerFunction(model, pos);
if (!container) return;
const containerRange = container.kind === SymbolKind.Method ? container.selectionRange : container.range;
this._addSnippetIfNotOverlapping(model, containerRange, snippets, visited);
const symbols = await this._getSymbolsNearRange(model, containerRange, numLines);
for (const sym of symbols) {
const defs = await this._getDefinitionSymbols(model, sym);
for (const def of defs) {
const defModel = this._modelService.getModel(def.uri);
if (defModel) {
const defPos = new Position(def.range.startLineNumber, def.range.startColumn);
this._addSnippetIfNotOverlapping(defModel, def.range, snippets, visited);
await this._gatherNearbySnippets(defModel, defPos, numLines, depth - 1, snippets, visited);
}
}
}
const containerPos = new Position(containerRange.startLineNumber, containerRange.startColumn);
await this._gatherParentSnippets(model, containerPos, numLines, depth - 1, snippets, visited);
}
private _isRangeVisited(uri: string, startLine: number, endLine: number, visited: IVisitedInterval[]): boolean {
return visited.some(interval =>
interval.uri === uri &&
!(endLine < interval.startLine || startLine > interval.endLine)
);
}
private async _getSymbolsNearPosition(model: ITextModel, pos: Position, numLines: number): Promise<DocumentSymbol[]> {
const startLine = Math.max(pos.lineNumber - numLines, 1);
const endLine = Math.min(pos.lineNumber + numLines, model.getLineCount());
const range = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine));
return this._getSymbolsInRange(model, range);
}
private async _getSymbolsNearRange(model: ITextModel, range: IRange, numLines: number): Promise<DocumentSymbol[]> {
const centerLine = Math.floor((range.startLineNumber + range.endLineNumber) / 2);
const startLine = Math.max(centerLine - numLines, 1);
const endLine = Math.min(centerLine + numLines, model.getLineCount());
const searchRange = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine));
return this._getSymbolsInRange(model, searchRange);
}
private async _getSymbolsInRange(model: ITextModel, range: IRange): Promise<DocumentSymbol[]> {
const symbols: DocumentSymbol[] = [];
const providers = this._langFeaturesService.documentSymbolProvider.ordered(model);
for (const provider of providers) {
try {
const result = await provider.provideDocumentSymbols(model, CancellationToken.None);
if (result) {
const flat = this._flattenSymbols(result);
const intersecting = flat.filter(sym => this._rangesIntersect(sym.range, range));
symbols.push(...intersecting);
}
} catch (e) {
console.warn("Symbol provider error:", e);
}
}
// Also check reference providers.
const refProviders = this._langFeaturesService.referenceProvider.ordered(model);
for (let line = range.startLineNumber; line <= range.endLineNumber; line++) {
const content = model.getLineContent(line);
const words = content.match(/[a-zA-Z_]\w*/g) || [];
for (const word of words) {
const startColumn = content.indexOf(word) + 1;
const pos = new Position(line, startColumn);
if (!this._positionInRange(pos, range)) continue;
for (const provider of refProviders) {
try {
const refs = await provider.provideReferences(model, pos, { includeDeclaration: true }, CancellationToken.None);
if (refs) {
const filtered = refs.filter(ref => this._rangesIntersect(ref.range, range));
for (const ref of filtered) {
symbols.push({
name: word,
detail: '',
kind: SymbolKind.Variable,
range: ref.range,
selectionRange: ref.range,
children: [],
tags: []
});
}
}
} catch (e) {
console.warn("Reference provider error:", e);
}
}
}
}
return symbols;
}
private _flattenSymbols(symbols: DocumentSymbol[]): DocumentSymbol[] {
const flat: DocumentSymbol[] = [];
for (const sym of symbols) {
flat.push(sym);
if (sym.children && sym.children.length > 0) {
flat.push(...this._flattenSymbols(sym.children));
}
}
return flat;
}
private _rangesIntersect(a: IRange, b: IRange): boolean {
return !(
a.endLineNumber < b.startLineNumber ||
a.startLineNumber > b.endLineNumber ||
(a.endLineNumber === b.startLineNumber && a.endColumn < b.startColumn) ||
(a.startLineNumber === b.endLineNumber && a.endColumn > b.endColumn)
);
}
private _positionInRange(pos: Position, range: IRange): boolean {
return pos.lineNumber >= range.startLineNumber &&
pos.lineNumber <= range.endLineNumber &&
(pos.lineNumber !== range.startLineNumber || pos.column >= range.startColumn) &&
(pos.lineNumber !== range.endLineNumber || pos.column <= range.endColumn);
}
// Get definition symbols for a given symbol.
private async _getDefinitionSymbols(model: ITextModel, symbol: DocumentSymbol): Promise<(DocumentSymbol & { uri: URI })[]> {
const pos = new Position(symbol.range.startLineNumber, symbol.range.startColumn);
const providers = this._langFeaturesService.definitionProvider.ordered(model);
const defs: (DocumentSymbol & { uri: URI })[] = [];
for (const provider of providers) {
try {
const res = await provider.provideDefinition(model, pos, CancellationToken.None);
if (res) {
const links = Array.isArray(res) ? res : [res];
defs.push(...links.map(link => ({
name: symbol.name,
detail: symbol.detail,
kind: symbol.kind,
range: link.range,
selectionRange: link.range,
children: [],
tags: symbol.tags || [],
uri: link.uri // Now keeping it as URI instead of converting to string
})));
}
} catch (e) {
console.warn("Definition provider error:", e);
}
}
return defs;
}
private async _findContainerFunction(model: ITextModel, pos: Position): Promise<DocumentSymbol | null> {
const searchRange = new Range(
Math.max(pos.lineNumber - 1, 1), 1,
Math.min(pos.lineNumber + 1, model.getLineCount()),
model.getLineMaxColumn(pos.lineNumber)
);
const symbols = await this._getSymbolsInRange(model, searchRange);
const funcs = symbols.filter(s =>
(s.kind === SymbolKind.Function || s.kind === SymbolKind.Method) &&
this._positionInRange(pos, s.range)
);
if (!funcs.length) return null;
return funcs.reduce((innermost, current) => {
if (!innermost) return current;
const moreInner =
(current.range.startLineNumber > innermost.range.startLineNumber ||
(current.range.startLineNumber === innermost.range.startLineNumber &&
current.range.startColumn > innermost.range.startColumn)) &&
(current.range.endLineNumber < innermost.range.endLineNumber ||
(current.range.endLineNumber === innermost.range.endLineNumber &&
current.range.endColumn < innermost.range.endColumn));
return moreInner ? current : innermost;
}, null as DocumentSymbol | null);
}
}
registerSingleton(IContextGatheringService, ContextGatheringService, InstantiationType.Eager);

View file

@ -0,0 +1,258 @@
const root = () => { }
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
type ChangeType = { name: string; age: number };
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
type EditType = { name: string; age: number };
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
const fn = (params: { edit: EditType, change: ChangeType }) => {
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
const r = root()
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
}
const testVar = 5;
class MyClass {
private test1(t: EditType) {
//
//
//
////
//
//
////
//
//
////
//
//
////
//
//
////
//
//
////
//
//
////
//
//
////
//
//
////
//
//
////
//x
//
//
const x = 1
}
}
const

View file

@ -21,6 +21,10 @@ import './chatThreadService.js'
// register Autocomplete
import './autocompleteService.js'
// register Context services
import './contextGatheringService.js'
import './contextUserChangesService.js'
// settings pane
import './voidSettingsPane.js'