Skip to content

Commit

Permalink
feat(chat): add listFileInWorkspace and readFileContent APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
Sma1lboy committed Jan 6, 2025
1 parent 872d8b1 commit ccad4a3
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 52 deletions.
2 changes: 2 additions & 0 deletions clients/vscode/src/chat/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export function createClient(webview: Webview, api: ClientApiMethods): ServerApi
openExternal: api.openExternal,
readWorkspaceGitRepositories: api.readWorkspaceGitRepositories,
getActiveEditorSelection: api.getActiveEditorSelection,
listFileInWorkspace: api.listFileInWorkspace,
readFileContent: api.readFileContent,
},
});
}
100 changes: 48 additions & 52 deletions clients/vscode/src/chat/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import path from "path";
import {
Position as VSCodePosition,
Range as VSCodeRange,
Uri,
workspace,
DocumentSymbol,
SymbolInformation,
SymbolKind,
TextEditor,
} from "vscode";
import type { Filepath, Position as ChatPanelPosition, LineRange, PositionRange, Location } from "tabby-chat-panel";
import { Position as VSCodePosition, Range as VSCodeRange, Uri, workspace, TextEditor, TextDocument } from "vscode";
import type {
Filepath,
Position as ChatPanelPosition,
LineRange,
PositionRange,
Location,
ListFileItem,
FilepathInGitRepository,
} from "tabby-chat-panel";
import type { GitProvider } from "../git/GitProvider";
import { getLogger } from "../logger";

Expand Down Expand Up @@ -224,51 +223,48 @@ export function generateLocalNotebookCellUri(notebook: Uri, handle: number): Uri
return notebook.with({ scheme: DocumentSchemes.vscodeNotebookCell, fragment });
}

export function isDocumentSymbol(symbol: DocumentSymbol | SymbolInformation): symbol is DocumentSymbol {
return "children" in symbol;
}

// FIXME: All allow symbol kinds, could be change later
export function getAllowedSymbolKinds(): SymbolKind[] {
return [
SymbolKind.Class,
SymbolKind.Function,
SymbolKind.Method,
SymbolKind.Interface,
SymbolKind.Enum,
SymbolKind.Struct,
];
}
export function uriToListFileItem(uri: Uri, gitProvider: GitProvider): ListFileItem {
const filepath = localUriToChatPanelFilepath(uri, gitProvider);
let label: string;

export function vscodeSymbolToSymbolAtInfo(
symbol: DocumentSymbol | SymbolInformation,
documentUri: Uri,
gitProvider: GitProvider,
): SymbolAtInfo {
if (isDocumentSymbol(symbol)) {
return {
atKind: "symbol",
name: symbol.name,
location: {
filepath: localUriToChatPanelFilepath(documentUri, gitProvider),
location: vscodeRangeToChatPanelPositionRange(symbol.range),
},
};
if (filepath.kind === "git") {
label = filepath.filepath;
} else {
const workspaceFolder = workspace.getWorkspaceFolder(uri);
if (workspaceFolder) {
label = path.relative(workspaceFolder.uri.fsPath, uri.fsPath);
} else {
label = path.basename(uri.fsPath);
}
}

return {
atKind: "symbol",
name: symbol.name,
location: {
filepath: localUriToChatPanelFilepath(documentUri, gitProvider),
location: vscodeRangeToChatPanelPositionRange(symbol.location.range),
},
label,
filepath,
};
}

export function uriToFileAtFileInfo(uri: Uri, gitProvider: GitProvider): FileAtInfo {
return {
atKind: "file",
name: path.basename(uri.fsPath),
filepath: localUriToChatPanelFilepath(uri, gitProvider),
};
export function extractTextFromRange(document: TextDocument, range: LineRange | PositionRange): string {
if (typeof range.start === "number" && typeof range.end === "number") {
const startLine = range.start - 1;
const endLine = range.end - 1;
let selectedText = "";

for (let i = startLine; i <= endLine; i++) {
if (i < 0 || i >= document.lineCount) {
continue;
}
selectedText += document.lineAt(i).text + "\n";
}
return selectedText;
}

if (typeof range.start === "object" && typeof range.end === "object") {
const startPos = new VSCodePosition(range.start.line - 1, range.start.character - 1);
const endPos = new VSCodePosition(range.end.line - 1, range.end.character - 1);
const selectedRange = new VSCodeRange(startPos, endPos);
return document.getText(selectedRange);
}

return "";
}
59 changes: 59 additions & 0 deletions clients/vscode/src/chat/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import type {
FileLocation,
GitRepository,
EditorFileContext,
ListFilesInWorkspaceParams,
ListFileItem,
FileRange,
} from "tabby-chat-panel";
import * as semver from "semver";
import type { StatusInfo, Config } from "tabby-agent";
Expand All @@ -42,6 +45,8 @@ import {
vscodeRangeToChatPanelPositionRange,
chatPanelLocationToVSCodeRange,
isValidForSyncActiveEditorSelection,
uriToListFileItem,
extractTextFromRange,
} from "./utils";
import mainHtml from "./html/main.html";
import errorHtml from "./html/error.html";
Expand Down Expand Up @@ -456,6 +461,60 @@ export class ChatWebview {
const fileContext = await getFileContextFromSelection(editor, this.gitProvider);
return fileContext;
},
listFileInWorkspace: async (params: ListFilesInWorkspaceParams): Promise<ListFileItem[]> => {
const maxResults = params.limit || 50;
const query = params.query?.toLowerCase();

if (!query) {
const documents = workspace.textDocuments;
this.logger.info(`No query provided, listing ${documents.length} opened editors.`);
return documents
.filter((doc) => doc.uri.scheme === "file")
.map((document) => uriToListFileItem(document.uri, this.gitProvider));
}

const globPattern = `**/${query}*`;
this.logger.info(`Searching files with pattern: ${globPattern}, limit: ${maxResults}.`);
try {
const files = await workspace.findFiles(globPattern, null, maxResults);
this.logger.info(`Found ${files.length} files.`);
return files.map((uri) => uriToListFileItem(uri, this.gitProvider));
} catch (error) {
this.logger.warn("Failed to find files:", error);
return [];
}
},

readFileContent: async (info: FileRange): Promise<string | null> => {
if (info.range) {
try {
const uri = chatPanelFilepathToLocalUri(info.filepath, this.gitProvider);
if (!uri) {
this.logger.warn(`Could not resolve URI from filepath: ${JSON.stringify(info.filepath)}`);
return null;
}
const document = await workspace.openTextDocument(uri);

return extractTextFromRange(document, info.range);
} catch (error) {
this.logger.error("Failed to get file content by range:", error);
return null;
}
} else {
try {
const uri = chatPanelFilepathToLocalUri(info.filepath, this.gitProvider);
if (!uri) {
this.logger.warn(`Could not resolve URI from filepath: ${JSON.stringify(info.filepath)}`);
return null;
}
const document = await workspace.openTextDocument(uri);
return document.getText();
} catch (error) {
this.logger.error("Failed to get file content:", error);
return null;
}
}
},
});
}

Expand Down

0 comments on commit ccad4a3

Please sign in to comment.