links to code

This commit is contained in:
Mathew Pareles 2025-03-08 01:01:16 -08:00
parent 4a9c0c864f
commit 2300559081
2 changed files with 77 additions and 100 deletions

View file

@ -293,7 +293,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// should probably re-use code from void/src/vs/base/common/marshalling.ts instead. but this is simple enough
private _convertThreadDataFromStorage(threadsStr: string): ChatThreads {
return JSON.parse(threadsStr, (key, value) => {
if (value && typeof value === 'object' && value.$mid === 1) { //$mid is the MarshalledId. $mid === 1 means it is a URI
if (value && typeof value === 'object' && value.$mid === 1) { // $mid is the MarshalledId. $mid === 1 means it is a URI
return URI.from(value);
}
return value;
@ -587,22 +587,27 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// process codespan to understand what we are searching for
// TODO account for more complicated patterns eg `ITextEditorService.openEditor()`
const filePattern = /^[^\s.]+\.[^\s.]+$/;
const functionPattern = /^[^\s(]+\([^)]*\)$/;
const functionOrMethodPattern = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; // `fUnCt10n_name`
const functionParensPattern = /^([^\s(]+)\([^)]*\)$/; // `functionName( args )`
let target = _codespanStr // the string to search for
let codespanType: 'file' | 'function-or-class' | 'unsearchable' = 'unsearchable';
if (filePattern.test(target)) {
if (target.includes('.')) {
codespanType = 'file'
target = _codespanStr
} else if (functionPattern.test(target)) {
const match = target.match(functionPattern)
if (match && match[0]) {
} else if (functionOrMethodPattern.test(target)) {
codespanType = 'function-or-class'
target = _codespanStr
} else if (functionParensPattern.test(target)) {
const match = target.match(functionParensPattern)
if (match && match[1]) {
codespanType = 'function-or-class'
target = match[0]
target = match[1]
}
}
@ -618,7 +623,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
.reverse()
if (codespanType === 'file') {
@ -650,96 +654,79 @@ class ChatThreadService extends Disposable implements IChatThreadService {
try {
const matches = model.findMatches(
target.split('(')[0].trim(), // remove parameters
target,
false, // searchOnlyEditableRange
false, // isRegex
true, // matchCase
null, // wordSeparators
' ', // wordSeparators
true // captureMatches
);
const firstThree = matches.slice(0, 3);
// take first 3 occurences, attempt to goto definition on them
for (const match of firstThree) {
const position = new Position(match.range.startLineNumber, match.range.startColumn);
const definitionProviders = this._languageFeaturesService.definitionProvider.ordered(model);
for (const provider of definitionProviders) {
const definitions = await provider.provideDefinition(model, position, CancellationToken.None);
if (!definitions) continue;
const locationLinks: LocationLink[] = [];
const _definitions = await provider.provideDefinition(model, position, CancellationToken.None);
if (Array.isArray(definitions)) {
// Handle Location[] or LocationLink[]
for (const def of definitions) {
if ('uri' in def) {
locationLinks.push({
uri: def.uri,
range: def.range,
targetSelectionRange: def.range,
originSelectionRange: undefined
});
} else {
locationLinks.push(def);
if (!_definitions) continue;
const definitions = Array.isArray(_definitions) ? _definitions : [_definitions];
for (const definition of definitions) {
return {
uri: definition.uri,
selection: {
startLineNumber: definition.range.startLineNumber,
startColumn: definition.range.startColumn,
endLineNumber: definition.range.endLineNumber,
endColumn: definition.range.endColumn,
}
}
} else {
// Handle single Location
locationLinks.push({
uri: definitions.uri,
range: definitions.range,
targetSelectionRange: definitions.range,
originSelectionRange: undefined
});
}
};
const definition = locationLinks[0];
if (!definition) continue;
// const defModelRef = await this._textModelService.createModelReference(definition.uri);
// const defModel = defModelRef.object.textEditorModel;
// Load definition file model
const defModelRef = await this._textModelService.createModelReference(definition.uri);
const defModel = defModelRef.object.textEditorModel;
// try {
// const symbolProviders = this._languageFeaturesService.documentSymbolProvider.ordered(defModel);
try {
const symbolProviders = this._languageFeaturesService.documentSymbolProvider.ordered(defModel);
// for (const symbolProvider of symbolProviders) {
// const symbols = await symbolProvider.provideDocumentSymbols(
// defModel,
// CancellationToken.None
// );
for (const symbolProvider of symbolProviders) {
const symbols = await symbolProvider.provideDocumentSymbols(
defModel,
CancellationToken.None
);
// if (symbols) {
// const symbol = symbols.find(s => {
// const symbolRange = s.range;
// return symbolRange.startLineNumber <= definition.range.startLineNumber &&
// symbolRange.endLineNumber >= definition.range.endLineNumber &&
// (symbolRange.startLineNumber !== definition.range.startLineNumber || symbolRange.startColumn <= definition.range.startColumn) &&
// (symbolRange.endLineNumber !== definition.range.endLineNumber || symbolRange.endColumn >= definition.range.endColumn);
// });
if (symbols) {
const symbol = symbols.find(s => {
const symbolRange = s.range;
return symbolRange.startLineNumber <= definition.range.startLineNumber &&
symbolRange.endLineNumber >= definition.range.endLineNumber &&
(symbolRange.startLineNumber !== definition.range.startLineNumber || symbolRange.startColumn <= definition.range.startColumn) &&
(symbolRange.endLineNumber !== definition.range.endLineNumber || symbolRange.endColumn >= definition.range.endColumn);
});
console.log('@@@ symbol', symbol?.name, symbol?.kind)
// if we got to a class/function get the full range and return
if (symbol?.kind === SymbolKind.Function || symbol?.kind === SymbolKind.Class) {
return {
uri: definition.uri,
selection: {
startLineNumber: definition.range.startLineNumber,
startColumn: definition.range.startColumn,
endLineNumber: definition.range.endLineNumber,
endColumn: definition.range.endColumn,
}
};
}
}
}
} finally {
defModelRef.dispose();
// // if we got to a class/function get the full range and return
// if (symbol?.kind === SymbolKind.Function || symbol?.kind === SymbolKind.Method || symbol?.kind === SymbolKind.Class) {
// return {
// uri: definition.uri,
// selection: {
// startLineNumber: definition.range.startLineNumber,
// startColumn: definition.range.startColumn,
// endLineNumber: definition.range.endLineNumber,
// endColumn: definition.range.endColumn,
// }
// };
// }
// }
// }
// } finally {
// defModelRef.dispose();
// }
}
}
}
@ -763,10 +750,9 @@ class ChatThreadService extends Disposable implements IChatThreadService {
const links = thread.state.linksOfMessageIdx?.[messageIdx]
if (!links) return undefined;
const location = links[codespanStr]
if (!location) return undefined;
const link = links[codespanStr]
return location
return link
}
async addCodespanLink({ newLinkText, newLinkLocation, messageIdx, threadId }: { newLinkText: string, newLinkLocation: CodespanLocationLink, messageIdx: number, threadId: string }) {

View file

@ -50,26 +50,20 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
console.log('rerender', didComputeCodespanLink ? 1 : 0)
let link = undefined
if (rawText.endsWith("`")) { // if codespan was completed
// get link from cache
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
if (link === undefined) {
// generate link and add to cache
chatThreadService.generateCodespanLink(text)
(chatThreadService.generateCodespanLink(text)
.then(link => {
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
setDidComputeCodespanLink(true)
setDidComputeCodespanLink(true) // rerender
})
)
}
}
@ -83,22 +77,22 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
// open the file
commandSerivce.executeCommand('vscode.open', link.uri).then(() => {
console.log('click:', selection, link.uri)
// select the text
if (!selection) return;
setTimeout(() => {
if (!selection) return;
const editor = editorService.getActiveCodeEditor()
if (!editor) return;
const editor = editorService.getActiveCodeEditor()
if (!editor) return;
editor.setSelection(selection)
editor.revealRange(selection, ScrollType.Immediate)
editor.setSelection(selection)
editor.revealRange(selection, ScrollType.Immediate)
}, 50) // needed when document was just opened and needs to initialize
})
}
return <Codespan
text={text}
onClick={onClick}
@ -311,9 +305,6 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
// inline code
if (t.type === "codespan") {
console.log('chatmessagelocation', chatMessageLocation)
if (chatMessageLocation) {
return <CodespanWithLink
text={t.text}