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: adding clickble symbol render in chat panel #3420

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion clients/tabby-chat-panel/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ export interface ServerApi {
updateTheme: (style: string, themeClass: string) => void
updateActiveSelection: (context: Context | null) => void
}

export interface KeywordInfo {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export interface KeywordInfo {
export interface SymbolInfo {

sourceFile: string
sourceLine: number
sourceChar: number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sourceChar: number
sourceCol: number

targetFile: string
targetLine: number
targetChar: number
}
export interface ClientApi {
navigate: (context: Context, opts?: NavigateOpts) => void
refresh: () => Promise<void>
Expand All @@ -69,6 +76,10 @@ export interface ClientApi {
onCopy: (content: string) => void

onKeyboardEvent: (type: 'keydown' | 'keyup' | 'keypress', event: KeyboardEventInit) => void
onRenderLsp: (filepaths: string[], keywords: string[]) => Promise<Record<
Copy link
Member

@wsxiaoys wsxiaoys Nov 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onRenderLsp: (filepaths: string[], keywords: string[]) => Promise<Record<
onNavigateSymbol: (filepath: String, symbol: String) => void

Instead of return SymbolInfo, why not just navigate to the symbol directly?

string,
KeywordInfo
>>
}

export interface ChatMessage {
Expand All @@ -94,6 +105,7 @@ export function createClient(target: HTMLIFrameElement, api: ClientApi): ServerA
onLoaded: api.onLoaded,
onCopy: api.onCopy,
onKeyboardEvent: api.onKeyboardEvent,
onRenderLsp: api.onRenderLsp,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onRenderLsp: api.onRenderLsp,
onNavigateSymbol: api.onNavigateSymbol,

},
})
}
Expand Down
86 changes: 86 additions & 0 deletions clients/vscode/src/chat/WebviewHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
Webview,
ColorThemeKind,
ProgressLocation,
commands,
LocationLink,
} from "vscode";
import type { ServerApi, ChatMessage, Context, NavigateOpts, OnLoadedParams } from "tabby-chat-panel";
import { TABBY_CHAT_PANEL_API_VERSION } from "tabby-chat-panel";
Expand All @@ -23,6 +25,7 @@ import { createClient } from "./chatPanel";
import { ChatFeature } from "../lsp/ChatFeature";
import { isBrowser } from "../env";
import { getFileContextFromSelection, showFileContext } from "./fileContext";
import path from "path";

export class WebviewHelper {
webview?: Webview;
Expand Down Expand Up @@ -548,6 +551,89 @@ export class WebviewHelper {
this.logger.debug(`Dispatching keyboard event: ${type} ${JSON.stringify(event)}`);
this.webview?.postMessage({ action: "dispatchKeyboardEvent", type, event });
},
onRenderLsp: async (filepaths: string[], keywords: string[]) => {
const foundLocations: Record<
string,
{
sourceFile: string;
sourceLine: number;
sourceChar: number;
targetFile: string;
targetLine: number;
targetChar: number;
}
> = {};

if (!keywords.length || !filepaths.length || filepaths.length === 0) {
this.logger.info("No keywords or filepaths provided");
return foundLocations;
}

const foundKeywords = new Set<string>();

try {
const workspaceRoot = workspace.workspaceFolders?.[0];
if (!workspaceRoot) {
this.logger.error("No workspace folder found");
return foundLocations;
}
const rootPath = workspaceRoot.uri;

for (const filepath of filepaths) {
if (foundKeywords.size === keywords.length) {
this.logger.info("All keywords found, stopping search");
break;
}

const normalizedPath = filepath.startsWith("/") ? filepath.slice(1) : filepath;
const fullPath = path.join(rootPath.path, normalizedPath);
const fileUri = Uri.file(fullPath);
const document = await workspace.openTextDocument(fileUri);

const remainingKeywords = keywords.filter((kw) => !foundKeywords.has(kw));

for (const keyword of remainingKeywords) {
if (foundLocations[keyword]) continue;

const content = document.getText();
const pos = content.indexOf(keyword);

if (pos !== -1) {
const position = document.positionAt(pos);
const locations = await commands.executeCommand<LocationLink[]>(
"vscode.executeDefinitionProvider",
fileUri,
position,
);

if (locations && locations.length > 0) {
// TODO: handle multiple locations
const location = locations[0];
if (location) {
foundLocations[keyword] = {
sourceFile: filepath,
sourceLine: position.line + 1,
sourceChar: position.character,
targetFile: workspace.asRelativePath(location.targetUri.path),
targetLine: location.targetRange.start.line,
targetChar: location.targetRange.start.character,
};
foundKeywords.add(keyword);
}
}
}
}
}

// TODO: test
for (const [keyword, location] of Object.entries(foundLocations)) {
this.logger.info(`Found locations for ${keyword}:`, location);
}
} catch (error) {
this.logger.error("Error in onRenderLsp:", error);
}
return foundLocations;
},
});
}
}
Expand Down
1 change: 1 addition & 0 deletions clients/vscode/src/chat/chatPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function createClient(webview: Webview, api: ClientApi): ServerApi {
onCopy: api.onCopy,
onLoaded: api.onLoaded,
onKeyboardEvent: api.onKeyboardEvent,
onRenderLsp: api.onRenderLsp,
},
});
}
1 change: 1 addition & 0 deletions ee/tabby-ui/app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ export default function ChatPage() {
onCopyContent={isInEditor && server?.onCopy}
onSubmitMessage={isInEditor && server?.onSubmitMessage}
onApplyInEditor={isInEditor && server?.onApplyInEditor}
onRenderLsp={isInEditor && server?.onRenderLsp}
/>
</ErrorBoundary>
)
Expand Down
17 changes: 16 additions & 1 deletion ee/tabby-ui/components/chat/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { RefObject } from 'react'
import { compact, findIndex, isEqual, some, uniqWith } from 'lodash-es'
import type { Context, FileContext, NavigateOpts } from 'tabby-chat-panel'
import type {
Context,
FileContext,
KeywordInfo,
NavigateOpts
} from 'tabby-chat-panel'

import { ERROR_CODE_NOT_FOUND } from '@/lib/constants'
import {
Expand Down Expand Up @@ -46,6 +51,10 @@ type ChatContextValue = {
content: string,
opts?: { languageId: string; smart: boolean }
) => void
onRenderLsp?: (
filepaths: string[],
keywords: string[]
) => Promise<Record<string, KeywordInfo>>
relevantContext: Context[]
activeSelection: Context | null
removeRelevantContext: (index: number) => void
Expand Down Expand Up @@ -84,6 +93,10 @@ interface ChatProps extends React.ComponentProps<'div'> {
content: string,
opts?: { languageId: string; smart: boolean }
) => void
onRenderLsp?: (
filepaths: string[],
keywords: string[]
) => Promise<Record<string, KeywordInfo>>
chatInputRef: RefObject<HTMLTextAreaElement>
}

Expand All @@ -104,6 +117,7 @@ function ChatRenderer(
onCopyContent,
onSubmitMessage,
onApplyInEditor,
onRenderLsp,
chatInputRef
}: ChatProps,
ref: React.ForwardedRef<ChatRef>
Expand Down Expand Up @@ -522,6 +536,7 @@ function ChatRenderer(
container,
onCopyContent,
onApplyInEditor,
onRenderLsp,
relevantContext,
removeRelevantContext,
chatInputRef,
Expand Down
4 changes: 3 additions & 1 deletion ee/tabby-ui/components/chat/question-answer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ function AssistantMessageCard(props: AssistantMessageCardProps) {
enableRegenerating,
...rest
} = props
const { onNavigateToContext, onApplyInEditor, onCopyContent } =
const { onNavigateToContext, onApplyInEditor, onCopyContent, onRenderLsp } =
React.useContext(ChatContext)
const [relevantCodeHighlightIndex, setRelevantCodeHighlightIndex] =
React.useState<number | undefined>(undefined)
Expand Down Expand Up @@ -389,6 +389,8 @@ function AssistantMessageCard(props: AssistantMessageCardProps) {
onCodeCitationMouseEnter={onCodeCitationMouseEnter}
onCodeCitationMouseLeave={onCodeCitationMouseLeave}
canWrapLongLines={!isLoading}
onRenderLsp={onRenderLsp}
onNavigateToContext={onNavigateToContext}
/>
{!!message.error && <ErrorMessageBlock error={message.error} />}
</>
Expand Down
Loading
Loading