mirror of
https://github.com/voideditor/void
synced 2026-05-23 09:28:23 +00:00
changes
This commit is contained in:
parent
41057c20fc
commit
145fd9614b
5 changed files with 626 additions and 400 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
354
src/vs/workbench/contrib/void/browser/contextGatheringService.ts
Normal file
354
src/vs/workbench/contrib/void/browser/contextGatheringService.ts
Normal 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);
|
||||
258
src/vs/workbench/contrib/void/browser/test.ts
Normal file
258
src/vs/workbench/contrib/void/browser/test.ts
Normal 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
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue