Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vscode): add at feature with AT file implementation in VSCode #3602

Merged
merged 13 commits into from
Jan 17, 2025
Merged
2 changes: 2 additions & 0 deletions clients/vscode/src/chat/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export function createClient(webview: Webview, api: ClientApiMethods): ServerApi
getActiveEditorSelection: api.getActiveEditorSelection,
fetchSessionState: api.fetchSessionState,
storeSessionState: api.storeSessionState,
listFileInWorkspace: api.listFileInWorkspace,
readFileContent: api.readFileContent,
},
});
}
14 changes: 13 additions & 1 deletion clients/vscode/src/chat/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import path from "path";
import { TextEditor, Position as VSCodePosition, Range as VSCodeRange, Uri, workspace } from "vscode";
import { Position as VSCodePosition, Range as VSCodeRange, Uri, workspace, TextEditor } from "vscode";
import type {
Filepath,
Position as ChatPanelPosition,
LineRange,
PositionRange,
Location,
ListFileItem,
FilepathInGitRepository,
} from "tabby-chat-panel";
import type { GitProvider } from "../git/GitProvider";
Expand Down Expand Up @@ -221,3 +222,14 @@ export function generateLocalNotebookCellUri(notebook: Uri, handle: number): Uri
const fragment = `${p}${s}s${Buffer.from(notebook.scheme).toString("base64")}`;
return notebook.with({ scheme: DocumentSchemes.vscodeNotebookCell, fragment });
}

export function uriToListFileItem(uri: Uri, gitProvider: GitProvider): ListFileItem {
return {
filepath: localUriToChatPanelFilepath(uri, gitProvider),
};
}

export function escapeGlobPattern(query: string): string {
// escape special glob characters: * ? [ ] { } ( ) ! @
return query.replace(/[*?[\]{}()!@]/g, "\\$&");
}
54 changes: 54 additions & 0 deletions clients/vscode/src/chat/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ProgressLocation,
Location,
LocationLink,
TabInputText,
} from "vscode";
import { TABBY_CHAT_PANEL_API_VERSION } from "tabby-chat-panel";
import type {
Expand All @@ -26,6 +27,9 @@ import type {
FileLocation,
GitRepository,
EditorFileContext,
ListFilesInWorkspaceParams,
ListFileItem,
FileRange,
} from "tabby-chat-panel";
import * as semver from "semver";
import { v4 as uuid } from "uuid";
Expand All @@ -43,6 +47,8 @@ import {
vscodeRangeToChatPanelPositionRange,
chatPanelLocationToVSCodeRange,
isValidForSyncActiveEditorSelection,
uriToListFileItem,
escapeGlobPattern,
} from "./utils";
import mainHtml from "./html/main.html";
import errorHtml from "./html/error.html";
Expand Down Expand Up @@ -492,6 +498,54 @@ export class ChatWebview {
...state,
};
},

listFileInWorkspace: async (params: ListFilesInWorkspaceParams): Promise<ListFileItem[]> => {
const maxResults = params.limit || 50;
const searchQuery = params.query?.trim();

if (!searchQuery) {
const openTabs = window.tabGroups.all
.flatMap((group) => group.tabs)
.filter((tab) => tab.input && (tab.input as TabInputText).uri);

this.logger.info(`No query provided, listing ${openTabs.length} opened editors.`);
return openTabs.map((tab) => uriToListFileItem((tab.input as TabInputText).uri, this.gitProvider));
}

try {
const caseInsensitivePattern = searchQuery
.split("")
.map((char) => {
if (char.toLowerCase() !== char.toUpperCase()) {
return `{${char.toLowerCase()},${char.toUpperCase()}}`;
}
return escapeGlobPattern(char);
})
.join("");

const globPattern = `**/${caseInsensitivePattern}*`;

this.logger.info(`Searching files with pattern: ${globPattern}, limit: ${maxResults}`);

const files = await workspace.findFiles(globPattern, undefined, 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);
window.showErrorMessage("Failed to find files.");
return [];
}
},

readFileContent: async (info: FileRange): Promise<string | null> => {
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(chatPanelLocationToVSCodeRange(info.range) ?? undefined);
},
});
}

Expand Down
Loading