From 20e34e24e1c3ea32eafad70511a089bdc14c5783 Mon Sep 17 00:00:00 2001 From: Zhiming Ma Date: Wed, 8 Jan 2025 20:53:13 +0800 Subject: [PATCH] fix(eclipse): update chat panel api to 0.5.0. (#3651) * fix(eclipse): update chat panel api 0.5.0. * fix(eclipse): add selection context sync. --- clients/eclipse/feature/feature.xml | 4 +- clients/eclipse/plugin/META-INF/MANIFEST.MF | 2 +- .../tabby4eclipse/chat/ChatCommand.java | 8 + .../tabby4eclipse/chat/ChatMessage.java | 115 -------- .../tabbyml/tabby4eclipse/chat/ChatView.java | 250 ++++++++++------- .../tabby4eclipse/chat/ChatViewUtils.java | 257 ++++++++++++------ .../tabby4eclipse/chat/EditorFileContext.java | 31 +++ .../tabby4eclipse/chat/FileLocation.java | 19 ++ .../tabbyml/tabby4eclipse/chat/Filepath.java | 18 ++ .../chat/FilepathInGitRepository.java | 30 ++ .../tabby4eclipse/chat/FilepathUri.java | 14 + .../tabby4eclipse/chat/GitRepository.java | 13 + .../tabbyml/tabby4eclipse/chat/LineRange.java | 19 ++ .../tabbyml/tabby4eclipse/chat/Position.java | 19 ++ .../tabby4eclipse/chat/PositionRange.java | 19 ++ .../com/tabbyml/tabby4eclipse/chat/Range.java | 4 + .../git/EclipseJGitProvider.java | 4 +- 17 files changed, 529 insertions(+), 297 deletions(-) create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatCommand.java delete mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatMessage.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/EditorFileContext.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FileLocation.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Filepath.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathInGitRepository.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathUri.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/GitRepository.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/LineRange.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Position.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/PositionRange.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Range.java diff --git a/clients/eclipse/feature/feature.xml b/clients/eclipse/feature/feature.xml index 5d0505a199b8..6555cd4f7543 100644 --- a/clients/eclipse/feature/feature.xml +++ b/clients/eclipse/feature/feature.xml @@ -2,7 +2,7 @@ @@ -19,6 +19,6 @@ + version="0.0.2.31"/> diff --git a/clients/eclipse/plugin/META-INF/MANIFEST.MF b/clients/eclipse/plugin/META-INF/MANIFEST.MF index 91fa845d4d87..9c9e3de5e32c 100644 --- a/clients/eclipse/plugin/META-INF/MANIFEST.MF +++ b/clients/eclipse/plugin/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Tabby Plugin for Eclipse Bundle-SymbolicName: com.tabbyml.tabby4eclipse;singleton:=true -Bundle-Version: 0.0.2.30 +Bundle-Version: 0.0.2.31 Bundle-Activator: com.tabbyml.tabby4eclipse.Activator Bundle-Vendor: com.tabbyml Require-Bundle: org.eclipse.ui, diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatCommand.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatCommand.java new file mode 100644 index 000000000000..6c4c4e0560ae --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatCommand.java @@ -0,0 +1,8 @@ +package com.tabbyml.tabby4eclipse.chat; + +public abstract class ChatCommand { + public static final String EXPLAIN = "explain"; + public static final String FIX = "fix"; + public static final String GENERATE_DOCS = "generate-docs"; + public static final String GENERATE_TESTS = "generate-tests"; +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatMessage.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatMessage.java deleted file mode 100644 index d79d715ec245..000000000000 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatMessage.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.tabbyml.tabby4eclipse.chat; - -import java.util.List; - -import com.google.gson.annotations.SerializedName; - -public class ChatMessage { - private String message; - private FileContext selectContext; - private List relevantContext; - private FileContext activeContext; - - public ChatMessage() { - } - - public ChatMessage(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public FileContext getSelectContext() { - return selectContext; - } - - public void setSelectContext(FileContext selectContext) { - this.selectContext = selectContext; - } - - public List getRelevantContext() { - return relevantContext; - } - - public void setRelevantContext(List relevantContext) { - this.relevantContext = relevantContext; - } - - public FileContext getActiveContext() { - return activeContext; - } - - public void setActiveContext(FileContext activeContext) { - this.activeContext = activeContext; - } - - public static class FileContext { - @SuppressWarnings("unused") - private String kind = "file"; - private LineRange range; - private String filepath; - private String content; - @SerializedName("git_url") - private String gitUrl; - - public FileContext() { - } - - public LineRange getRange() { - return range; - } - - public void setRange(LineRange range) { - this.range = range; - } - - public String getFilePath() { - return filepath; - } - - public void setFilePath(String filepath) { - this.filepath = filepath; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getGitUrl() { - return gitUrl; - } - - public void setGitUrl(String gitUrl) { - this.gitUrl = gitUrl; - } - - public static class LineRange { - private int start; - private int end; - - public LineRange(int start, int end) { - this.start = start; - this.end = end; - } - - public int getStart() { - return start; - } - - public int getEnd() { - return end; - } - } - } - -} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java index 1e61864d10d0..2a4595baa9b0 100644 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java @@ -13,6 +13,8 @@ import org.eclipse.core.runtime.Platform; import org.eclipse.jface.resource.ColorRegistry; import org.eclipse.jface.resource.FontRegistry; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.BrowserFunction; @@ -23,6 +25,8 @@ import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.themes.ITheme; @@ -31,10 +35,11 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.tabbyml.tabby4eclipse.Activator; +import com.tabbyml.tabby4eclipse.DebouncedRunnable; import com.tabbyml.tabby4eclipse.Logger; import com.tabbyml.tabby4eclipse.StringUtils; import com.tabbyml.tabby4eclipse.Utils; -import com.tabbyml.tabby4eclipse.chat.ChatMessage.FileContext; +import com.tabbyml.tabby4eclipse.editor.EditorUtils; import com.tabbyml.tabby4eclipse.lsp.LanguageServerService; import com.tabbyml.tabby4eclipse.lsp.ServerConfigHolder; import com.tabbyml.tabby4eclipse.lsp.StatusInfoHolder; @@ -66,7 +71,13 @@ public class ChatView extends ViewPart { private RGB bgActiveColor; private RGB fgColor; private RGB borderColor; + private RGB inputBorderColor; private RGB primaryColor; + private RGB primaryFgColor; + private RGB popoverColor; + private RGB popoverFgColor; + private RGB accentColor; + private RGB accentFgColor; private String font; private int fontSize = 13; @@ -85,7 +96,7 @@ public void completed(ProgressEvent event) { handleLoaded(); } }); - + injectFunctions(); load(); serverConfigHolder.addConfigDidChangeListener(() -> { @@ -94,8 +105,34 @@ public void completed(ProgressEvent event) { statusInfoHolder.addStatusDidChangeListener(() -> { reloadContent(false); }); + + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().addSelectionListener(new ISelectionListener() { + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + if (selection instanceof ITextSelection) { + syncActiveSelectionRunnable.call(); + } + } + }); } + private DebouncedRunnable syncActiveSelectionRunnable = new DebouncedRunnable(() -> { + if (!isChatPanelLoaded) { + return; + } + EditorUtils.asyncExec(() -> { + try { + chatPanelClientInvoke("updateActiveSelection", new ArrayList<>() { + { + add(ChatViewUtils.getSelectedTextAsEditorFileContext()); + } + }); + } catch (Exception e) { + // ignore + } + }); + }, 100); + @Override public void setFocus() { browser.forceFocus(); @@ -114,46 +151,34 @@ public void dispose() { } public void explainSelectedText() { - chatPanelClientInvoke("sendMessage", new ArrayList<>() { + chatPanelClientInvoke("executeCommand", new ArrayList<>() { { - ChatMessage chatMessage = new ChatMessage(); - chatMessage.setMessage(ChatViewUtils.PROMPT_EXPLAIN); - chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext()); - add(chatMessage); + add(ChatCommand.EXPLAIN); } }); } public void fixSelectedText() { // FIXME(@icycodes): collect the diagnostic message provided by IDE or LSP - chatPanelClientInvoke("sendMessage", new ArrayList<>() { + chatPanelClientInvoke("executeCommand", new ArrayList<>() { { - ChatMessage chatMessage = new ChatMessage(); - chatMessage.setMessage(ChatViewUtils.PROMPT_FIX); - chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext()); - add(chatMessage); + add(ChatCommand.FIX); } }); } public void generateDocsForSelectedText() { - chatPanelClientInvoke("sendMessage", new ArrayList<>() { + chatPanelClientInvoke("executeCommand", new ArrayList<>() { { - ChatMessage chatMessage = new ChatMessage(); - chatMessage.setMessage(ChatViewUtils.PROMPT_GENERATE_DOCS); - chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext()); - add(chatMessage); + add(ChatCommand.GENERATE_DOCS); } }); } public void generateTestsForSelectedText() { - chatPanelClientInvoke("sendMessage", new ArrayList<>() { + chatPanelClientInvoke("executeCommand", new ArrayList<>() { { - ChatMessage chatMessage = new ChatMessage(); - chatMessage.setMessage(ChatViewUtils.PROMPT_GENERATE_TESTS); - chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext()); - add(chatMessage); + add(ChatCommand.GENERATE_TESTS); } }); } @@ -161,7 +186,7 @@ public void generateTestsForSelectedText() { public void addSelectedTextAsContext() { chatPanelClientInvoke("addRelevantContext", new ArrayList<>() { { - add(ChatViewUtils.getSelectedTextAsFileContext()); + add(ChatViewUtils.getSelectedTextAsEditorFileContext()); } }); } @@ -169,7 +194,7 @@ public void addSelectedTextAsContext() { public void addActiveEditorAsContext() { chatPanelClientInvoke("addRelevantContext", new ArrayList<>() { { - add(ChatViewUtils.getActiveEditorAsFileContext()); + add(ChatViewUtils.getActiveEditorAsEditorFileContext()); } }); } @@ -178,11 +203,24 @@ private void setupThemeStyle() { ITheme currentTheme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); ColorRegistry colorRegistry = currentTheme.getColorRegistry(); bgColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_BG_START"); + isDark = (bgColor.red + bgColor.green + bgColor.blue) / 3 < 128; + bgActiveColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_BG_END"); fgColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_TEXT_COLOR"); - borderColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_INNER_KEYLINE_COLOR"); + borderColor = isDark ? new RGB(64, 64, 64) : new RGB(192, 192, 192); + inputBorderColor = borderColor; + primaryColor = colorRegistry.getRGB("org.eclipse.ui.workbench.LINK_COLOR"); - isDark = (bgColor.red + bgColor.green + bgColor.blue) / 3 < 128; + if (primaryColor == null) { + primaryColor = isDark ? new RGB(55, 148, 255) : new RGB(26, 133, 255); + } + primaryFgColor = new RGB(255, 255, 255); + popoverColor = bgActiveColor; + popoverFgColor = fgColor; + accentColor = isDark ? new RGB(4, 57, 94) + : new RGB((int) (bgActiveColor.red * 0.8), (int) (bgActiveColor.green * 0.8), + (int) (bgActiveColor.blue * 0.8)); + accentFgColor = fgColor; FontRegistry fontRegistry = currentTheme.getFontRegistry(); FontData[] fontData = fontRegistry.getFontData("org.eclipse.jface.textfont"); @@ -206,9 +244,27 @@ private String buildCss() { if (borderColor != null) { css += String.format("--border: %s;", StringUtils.toHsl(borderColor)); } + if (inputBorderColor != null) { + css += String.format("--input: %s;", StringUtils.toHsl(inputBorderColor)); + } if (primaryColor != null) { css += String.format("--primary: %s;", StringUtils.toHsl(primaryColor)); } + if (primaryFgColor != null) { + css += String.format("--primary-foreground: %s;", StringUtils.toHsl(primaryFgColor)); + } + if (popoverColor != null) { + css += String.format("--popover: %s;", StringUtils.toHsl(popoverColor)); + } + if (popoverFgColor != null) { + css += String.format("--popover-foreground: %s;", StringUtils.toHsl(popoverFgColor)); + } + if (accentColor != null) { + css += String.format("--accent: %s;", StringUtils.toHsl(accentColor)); + } + if (accentFgColor != null) { + css += String.format("--accent-foreground: %s;", StringUtils.toHsl(accentFgColor)); + } if (font != null) { css += String.format("font: %s;", font); } @@ -220,13 +276,14 @@ private List parseArguments(final Object[] arguments) { if (arguments.length < 1) { return List.of(); } - return gson.fromJson(arguments[0].toString(), new TypeToken>(){}); + return gson.fromJson(arguments[0].toString(), new TypeToken>() { + }); } - + private Object serializeResult(final Object result) { return gson.toJson(result); } - + private void injectFunctions() { browserFunctions.add(new BrowserFunction(browser, "handleTabbyChatPanelResponse") { @Override @@ -239,12 +296,12 @@ public Object function(Object[] arguments) { String uuid = (String) params.get(0); String errorMessage = (String) params.get(1); Object result = params.get(2); - + CompletableFuture future = pendingChatPanelRequest.remove(uuid); if (future == null) { return null; } - + if (errorMessage != null && !errorMessage.isEmpty()) { future.completeExceptionally(new Exception(errorMessage)); } else { @@ -253,7 +310,7 @@ public Object function(Object[] arguments) { return null; } }); - + browserFunctions.add(new BrowserFunction(browser, "handleReload") { @Override public Object function(Object[] arguments) { @@ -262,20 +319,6 @@ public Object function(Object[] arguments) { return null; } }); - - browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelNavigate") { - @Override - public Object function(Object[] arguments) { - List params = parseArguments(arguments); - logger.debug("tabbyChatPanelNavigate: " + params); - if (params.size() < 1) { - return null; - } - FileContext context = gson.fromJson(gson.toJson(params.get(0)), FileContext.class); - ChatViewUtils.navigateToFileContext(context); - return null; - } - }); browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelRefresh") { @Override @@ -285,36 +328,12 @@ public Object function(Object[] arguments) { return null; } }); - - browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOnSubmitMessage") { - @Override - public Object function(Object[] arguments) { - List params = parseArguments(arguments); - if (params.size() < 1) { - return null; - } - String message = (String) params.get(0); - List relevantContexts = params.size() > 1 - ? relevantContexts = gson.fromJson(gson.toJson(params.get(1)), new TypeToken>() { - }.getType()) - : null; - chatPanelClientInvoke("sendMessage", new ArrayList<>() { - { - ChatMessage chatMessage = new ChatMessage(); - chatMessage.setMessage(message); - chatMessage.setRelevantContext(relevantContexts); - chatMessage.setActiveContext(ChatViewUtils.getSelectedTextAsFileContext()); - add(chatMessage); - } - }); - return null; - } - }); browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOnApplyInEditor") { @Override public Object function(Object[] arguments) { List params = parseArguments(arguments); + logger.debug("tabbyChatPanelOnApplyInEditor: " + params); if (params.size() < 1) { return null; } @@ -328,6 +347,7 @@ public Object function(Object[] arguments) { @Override public Object function(Object[] arguments) { List params = parseArguments(arguments); + logger.debug("tabbyChatPanelOnLoaded: " + params); if (params.size() < 1) { return null; } @@ -349,6 +369,7 @@ public Object function(Object[] arguments) { @Override public Object function(Object[] arguments) { List params = parseArguments(arguments); + logger.debug("tabbyChatPanelOnCopy: " + params); if (params.size() < 1) { return null; } @@ -371,12 +392,56 @@ public Object function(Object[] arguments) { browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOpenInEditor") { @Override public Object function(Object[] arguments) { - // FIXME: Not implemented - return serializeResult(false); + List params = parseArguments(arguments); + logger.debug("tabbyChatPanelOpenInEditor: " + params); + if (params.size() < 1) { + return null; + } + FileLocation fileLocation = ChatViewUtils.asFileLocation(params.get(0)); + boolean success = ChatViewUtils.openInEditor(fileLocation); + Object result = serializeResult(success); + logger.debug("tabbyChatPanelOpenInEditor result: " + result); + return result; + } + }); + + browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOpenExternal") { + @Override + public Object function(Object[] arguments) { + List params = parseArguments(arguments); + logger.debug("tabbyChatPanelOpenExternal: " + params); + if (params.size() < 1) { + return null; + } + String url = (String) params.get(0); + ChatViewUtils.openExternal(url); + return null; + } + }); + + browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelReadWorkspaceGitRepositories") { + @Override + public Object function(Object[] arguments) { + logger.debug("tabbyChatPanelReadWorkspaceGitRepositories"); + List repositories = ChatViewUtils.readGitRepositoriesInWorkspace(); + Object result = serializeResult(repositories); + logger.debug("tabbyChatPanelReadWorkspaceGitRepositories result: " + result); + return result; + } + }); + + browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelGetActiveEditorSelection") { + @Override + public Object function(Object[] arguments) { + logger.debug("tabbyChatPanelGetActiveEditorSelection"); + EditorFileContext context = ChatViewUtils.getSelectedTextAsEditorFileContext(); + Object result = serializeResult(context); + logger.debug("tabbyChatPanelGetActiveEditorSelection result: " + result); + return result; } }); } - + private void load() { try { // Find chat panel html file @@ -462,7 +527,6 @@ private void updateContentToChatPanel() { showChatPanel(true); } - // execute js functions private void executeScript(String script) { @@ -470,7 +534,7 @@ private void executeScript(String script) { browser.execute(script); }); } - + private void showMessage(String message) { if (message != null) { executeScript(String.format("showMessage('%s')", message)); @@ -503,6 +567,13 @@ private void applyStyle() { private void initChatPanel() { isChatPanelLoaded = true; + browser.getDisplay().timerExec(100, () -> { + updateContentToChatPanel(); + pendingScripts.forEach((script) -> { + executeScript(script); + }); + pendingScripts.clear(); + }); chatPanelClientInvoke("init", new ArrayList<>() { { add(new HashMap<>() { @@ -522,15 +593,8 @@ private void initChatPanel() { add(isDark ? "dark" : "light"); } }); - browser.getDisplay().timerExec(100, () -> { - updateContentToChatPanel(); - pendingScripts.forEach((script) -> { - executeScript(script); - }); - pendingScripts.clear(); - }); } - + private String wrapJsFunction(String name) { return String.format( String.join("\n", @@ -538,44 +602,46 @@ private String wrapJsFunction(String name) { " return new Promise((resolve, reject) => {", " const paramsJson = JSON.stringify(args)", " const result = %s(paramsJson)", - " resolve(result)", + " resolve(JSON.parse(result))", " });", "}" ), name ); } - + private void createChatPanelClient() { String script = String.format( String.join("\n", "if (!window.tabbyChatPanelClient) {", " window.tabbyChatPanelClient = TabbyThreads.createThreadFromIframe(getChatPanel(), {", " expose: {", - " navigate: %s,", " refresh: %s,", - " onSubmitMessage: %s,", " onApplyInEditor: %s,", " onLoaded: %s,", " onCopy: %s,", " onKeyboardEvent: %s,", " openInEditor: %s,", + " openExternal: %s,", + " readWorkspaceGitRepositories: %s,", + " getActiveEditorSelection: %s,", " }", " })", "}" ), - wrapJsFunction("tabbyChatPanelNavigate"), wrapJsFunction("tabbyChatPanelRefresh"), - wrapJsFunction("tabbyChatPanelOnSubmitMessage"), wrapJsFunction("tabbyChatPanelOnApplyInEditor"), wrapJsFunction("tabbyChatPanelOnLoaded"), wrapJsFunction("tabbyChatPanelOnCopy"), wrapJsFunction("tabbyChatPanelOnKeyboardEvent"), - wrapJsFunction("tabbyChatPanelOpenInEditor") + wrapJsFunction("tabbyChatPanelOpenInEditor"), + wrapJsFunction("tabbyChatPanelOpenExternal"), + wrapJsFunction("tabbyChatPanelReadWorkspaceGitRepositories"), + wrapJsFunction("tabbyChatPanelGetActiveEditorSelection") ); executeScript(script); } - + private CompletableFuture chatPanelClientInvoke(String method, List params) { CompletableFuture future = new CompletableFuture<>(); String uuid = UUID.randomUUID().toString(); diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java index 8b9197a92f60..f3992776b9ec 100644 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java @@ -1,10 +1,15 @@ package com.tabbyml.tabby4eclipse.chat; import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Path; import org.eclipse.jface.text.IDocument; @@ -13,6 +18,7 @@ import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.program.Program; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPage; @@ -21,25 +27,22 @@ import org.eclipse.ui.ide.ResourceUtil; import org.eclipse.ui.texteditor.ITextEditor; +import com.google.gson.Gson; import com.tabbyml.tabby4eclipse.Logger; import com.tabbyml.tabby4eclipse.Version; -import com.tabbyml.tabby4eclipse.chat.ChatMessage.FileContext; import com.tabbyml.tabby4eclipse.editor.EditorUtils; import com.tabbyml.tabby4eclipse.git.GitProvider; -import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository; import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepositoryParams; public class ChatViewUtils { private static final String ID = "com.tabbyml.tabby4eclipse.views.chat"; private static final String MIN_SERVER_VERSION = "0.18.0"; - private static final String CHAT_PANEL_API_VERSION = "0.4.0"; + private static final String CHAT_PANEL_API_VERSION = "0.5.0"; private static Logger logger = new Logger("ChatView"); - public static final String PROMPT_EXPLAIN = "Explain the selected code:"; - public static final String PROMPT_FIX = "Identify and fix potential bugs in the selected code:"; - public static final String PROMPT_GENERATE_DOCS = "Generate documentation for the selected code:"; - public static final String PROMPT_GENERATE_TESTS = "Generate a unit test for the selected code:"; + private static final Gson gson = new Gson(); + private static final Map gitRemoteUrlToLocalRoot = new HashMap<>(); public static ChatView openChatView() { IWorkbenchPage page = EditorUtils.getActiveWorkbenchPage(); @@ -104,119 +107,131 @@ public static String checkChatPanelApiVersion(String version) { return null; } - public static FileContext getSelectedTextAsFileContext() { + public static EditorFileContext getSelectedTextAsEditorFileContext() { ITextEditor activeTextEditor = EditorUtils.getActiveTextEditor(); if (activeTextEditor == null) { return null; } - FileContext context = new FileContext(); + IFile file = ResourceUtil.getFile(activeTextEditor.getEditorInput()); + URI fileUri = file.getLocationURI(); ISelection selection = activeTextEditor.getSelectionProvider().getSelection(); if (selection instanceof ITextSelection textSelection) { if (!textSelection.isEmpty()) { String content = textSelection.getText(); if (!content.isBlank()) { - context.setContent(content); - context.setRange(new FileContext.LineRange(textSelection.getStartLine() + 1, - textSelection.getEndLine() + 1)); - } - } - } - if (context.getContent() == null) { - return null; - } - - IFile file = ResourceUtil.getFile(activeTextEditor.getEditorInput()); - URI fileUri = file.getLocationURI(); - if (file != null) { - GitRepository gitInfo = GitProvider.getInstance() - .getRepository(new GitRepositoryParams(fileUri.toString())); - IProject project = file.getProject(); - if (gitInfo != null) { - try { - context.setGitUrl(gitInfo.getRemoteUrl()); - String relativePath = new URI(gitInfo.getRoot()).relativize(fileUri).getPath(); - context.setFilePath(relativePath); - } catch (Exception e) { - logger.error("Failed to get git info.", e); + return new EditorFileContext(fileUriToChatPanelFilepath(fileUri), + new LineRange(textSelection.getStartLine() + 1, textSelection.getEndLine() + 1), content); } - } else if (project != null) { - URI projectRoot = project.getLocationURI(); - String relativePath = projectRoot.relativize(fileUri).getPath(); - context.setFilePath(relativePath); - } else { - context.setFilePath(fileUri.toString()); } } - return context; + return null; } - public static FileContext getActiveEditorAsFileContext() { + public static EditorFileContext getActiveEditorAsEditorFileContext() { ITextEditor activeTextEditor = EditorUtils.getActiveTextEditor(); if (activeTextEditor == null) { return null; } - FileContext context = new FileContext(); - - IDocument document = EditorUtils.getDocument(activeTextEditor); - context.setRange(new FileContext.LineRange(1, document.getNumberOfLines())); - context.setContent(document.get()); - IFile file = ResourceUtil.getFile(activeTextEditor.getEditorInput()); URI fileUri = file.getLocationURI(); - if (file != null) { - GitRepository gitInfo = GitProvider.getInstance() - .getRepository(new GitRepositoryParams(fileUri.toString())); - IProject project = file.getProject(); - if (gitInfo != null) { - try { - context.setGitUrl(gitInfo.getRemoteUrl()); - String relativePath = new URI(gitInfo.getRoot()).relativize(fileUri).getPath(); - context.setFilePath(relativePath); - } catch (Exception e) { - logger.error("Failed to get git info.", e); - } - } else if (project != null) { - URI projectRoot = project.getLocationURI(); - String relativePath = projectRoot.relativize(fileUri).getPath(); - context.setFilePath(relativePath); - } else { - context.setFilePath(fileUri.toString()); - } + IDocument document = EditorUtils.getDocument(activeTextEditor); + String content = document.get(); + if (!content.isBlank()) { + return new EditorFileContext(fileUriToChatPanelFilepath(fileUri), null, content); } - - return context; + return null; } - public static void navigateToFileContext(FileContext context) { - logger.info("Navigate to file: " + context.getFilePath() + ", line: " + context.getRange().getStart()); - // FIXME(@icycode): the base path could be a git repository root, but it cannot - // be determined here - IFile file = null; - ITextEditor activeTextEditor = EditorUtils.getActiveTextEditor(); - if (activeTextEditor != null) { - // try find file in the project of the active editor - IFile activeFile = ResourceUtil.getFile(activeTextEditor.getEditorInput()); - if (activeFile != null) { - file = activeFile.getProject().getFile(new Path(context.getFilePath())); - } - } else { - // try find file in the workspace - file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path(context.getFilePath())); + public static boolean openInEditor(FileLocation fileLocation) { + if (fileLocation == null) { + return false; } + Filepath filepath = fileLocation.getFilepath(); + URI fileUri = null; try { + switch (filepath.getKind()) { + case Filepath.Kind.URI: + FilepathUri filepathUri = (FilepathUri) filepath; + fileUri = new URI(filepathUri.getUri()); + break; + + case Filepath.Kind.GIT: + FilepathInGitRepository filepathInGit = (FilepathInGitRepository) filepath; + String gitLocalRoot = gitRemoteUrlToLocalRoot.get(filepathInGit.getGitUrl()); + if (gitLocalRoot != null) { + fileUri = new URI(gitLocalRoot + "/" + filepathInGit.getFilepath()); + } + break; + + default: + fileUri = null; + break; + } + + if (fileUri == null) { + throw new Exception("Cannot parse as file uri."); + } + + IFile file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path(fileUri.getPath())); if (file != null && file.exists()) { IEditorPart editorPart = IDE.openEditor(EditorUtils.getActiveWorkbenchPage(), file); + if (editorPart instanceof ITextEditor textEditor) { IDocument document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()); - int offset = document.getLineOffset(context.getRange().getStart() - 1); - textEditor.selectAndReveal(offset, 0); + Object location = fileLocation.getLocation(); + Position position; + + if (location instanceof Number lineNumberValue) { + position = new Position(lineNumberValue.intValue() - 1, 0); + } else if (location instanceof Position positionValue) { + position = new Position(positionValue.getLine() - 1, positionValue.getCharacter() - 1); + } else if (location instanceof LineRange lineRangeValue) { + position = new Position(lineRangeValue.getStart() - 1, 0); + } else if (location instanceof PositionRange positionRangeValue) { + position = new Position(positionRangeValue.getStart().getLine() - 1, + positionRangeValue.getStart().getCharacter() - 1); + } else { + position = null; + } + + if (position != null) { + int offset = document.getLineOffset(position.getLine()) + position.getCharacter(); + textEditor.selectAndReveal(offset, 0); + } } + return true; + } else { + return false; } } catch (Exception e) { - logger.error("Failed to navigate to file: " + context.getFilePath(), e); + logger.error("Failed to open in editor.", e); + return false; } } + public static void openExternal(String url) { + Program.launch(url); + } + + public static List readGitRepositoriesInWorkspace() { + List repositories = new ArrayList<>(); + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + IProject[] projects = workspaceRoot.getProjects(); + + for (IProject project : projects) { + try { + URI projectRootUri = project.getLocation().toFile().toURI(); + com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository repo = GitProvider.getInstance().getRepository(new GitRepositoryParams(projectRootUri.toString())); + if (repo != null) { + repositories.add(new GitRepository(repo.getRemoteUrl())); + } + } catch (Exception e) { + logger.warn("Error when read git repository.", e); + } + } + return repositories; + } + public static void setClipboardContent(String content) { Display display = Display.getCurrent(); if (display == null) { @@ -243,4 +258,76 @@ public static void applyContentInEditor(String content) { } } } + + public static Filepath fileUriToChatPanelFilepath(URI fileUri) { + String fileUriString = fileUri.toString(); + com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository gitRepo = GitProvider.getInstance().getRepository(new GitRepositoryParams(fileUriString)); + String gitUrl = (gitRepo != null) ? gitRepo.getRemoteUrl() : null; + if (gitUrl != null) { + gitRemoteUrlToLocalRoot.put(gitUrl, gitRepo.getRoot()); + } + + if (gitUrl != null && fileUriString.startsWith(gitRepo.getRoot())) { + try { + String relativePath = new URI(gitRepo.getRoot()).relativize(fileUri).getPath(); + return new FilepathInGitRepository(relativePath, gitUrl); + } catch (URISyntaxException e) { + return new FilepathUri(fileUriString); + } + } else { + return new FilepathUri(fileUriString); + } + } + + public static FileLocation asFileLocation(Object obj) { + if (!(obj instanceof Map)) { + return null; + } + + Map map = (Map) obj; + + if (!map.containsKey("filepath")) { + return null; + } + + Object filepathValue = map.get("filepath"); + Filepath filepath = null; + + if (filepathValue instanceof Map) { + Map filepathMap = (Map) filepathValue; + if (filepathMap.containsKey("kind")) { + String kind = (String) filepathMap.get("kind"); + if (Filepath.Kind.GIT.equals(kind)) { + filepath = gson.fromJson(gson.toJson(filepathValue), FilepathInGitRepository.class); + } else if (Filepath.Kind.URI.equals(kind)) { + filepath = gson.fromJson(gson.toJson(filepathValue), FilepathUri.class); + } + } + } + + if (filepath == null) { + return null; + } + + Object locationValue = map.get("location"); + Object location = null; + + if (locationValue instanceof Number) { + location = locationValue; + } else if (locationValue instanceof Map) { + Map locationMap = (Map) locationValue; + if (locationMap.containsKey("line")) { + location = gson.fromJson(gson.toJson(locationValue), Position.class); + } else if (locationMap.containsKey("start")) { + Object startValue = locationMap.get("start"); + if (startValue instanceof Number) { + location = gson.fromJson(gson.toJson(locationValue), LineRange.class); + } else if (startValue instanceof Map) { + location = gson.fromJson(gson.toJson(locationValue), PositionRange.class); + } + } + } + + return new FileLocation(filepath, location); + } } diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/EditorFileContext.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/EditorFileContext.java new file mode 100644 index 000000000000..a09fecc192a8 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/EditorFileContext.java @@ -0,0 +1,31 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class EditorFileContext { + private final String kind; + private final Filepath filepath; + private final Range range; + private final String content; + + public EditorFileContext(Filepath filepath, Range range, String content) { + this.kind = "file"; + this.filepath = filepath; + this.range = range; + this.content = content; + } + + public String getKind() { + return kind; + } + + public Filepath getFilepath() { + return filepath; + } + + public Range getRange() { + return range; + } + + public String getContent() { + return content; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FileLocation.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FileLocation.java new file mode 100644 index 000000000000..215595116add --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FileLocation.java @@ -0,0 +1,19 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class FileLocation { + private final Filepath filepath; + private final Object location; + + public FileLocation(Filepath filepath, Object location) { + this.filepath = filepath; + this.location = location; + } + + public Filepath getFilepath() { + return filepath; + } + + public Object getLocation() { + return location; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Filepath.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Filepath.java new file mode 100644 index 000000000000..8af078ecd86e --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Filepath.java @@ -0,0 +1,18 @@ +package com.tabbyml.tabby4eclipse.chat; + +public abstract class Filepath { + private final String kind; + + protected Filepath(String kind) { + this.kind = kind; + } + + public String getKind() { + return kind; + } + + public static class Kind { + public static final String GIT = "git"; + public static final String URI = "uri"; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathInGitRepository.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathInGitRepository.java new file mode 100644 index 000000000000..8c59edbbd12f --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathInGitRepository.java @@ -0,0 +1,30 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class FilepathInGitRepository extends Filepath { + private final String filepath; + private final String gitUrl; + private final String revision; + + public FilepathInGitRepository(String filepath, String gitUrl) { + this(filepath, gitUrl, null); + } + + public FilepathInGitRepository(String filepath, String gitUrl, String revision) { + super(Kind.GIT); + this.filepath = filepath; + this.gitUrl = gitUrl; + this.revision = revision; + } + + public String getFilepath() { + return filepath; + } + + public String getGitUrl() { + return gitUrl; + } + + public String getRevision() { + return revision; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathUri.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathUri.java new file mode 100644 index 000000000000..c47cfe6c6b9f --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathUri.java @@ -0,0 +1,14 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class FilepathUri extends Filepath { + private final String uri; + + public FilepathUri(String uri) { + super(Kind.URI); + this.uri = uri; + } + + public String getUri() { + return uri; + } +} \ No newline at end of file diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/GitRepository.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/GitRepository.java new file mode 100644 index 000000000000..32f5c2d31ce5 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/GitRepository.java @@ -0,0 +1,13 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class GitRepository { + private final String url; + + public GitRepository(String url) { + this.url = url; + } + + public String getUrl() { + return url; + } +} \ No newline at end of file diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/LineRange.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/LineRange.java new file mode 100644 index 000000000000..9fc0958077c4 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/LineRange.java @@ -0,0 +1,19 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class LineRange extends Range { + private final int start; + private final int end; + + public LineRange(int start, int end) { + this.start = start; + this.end = end; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Position.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Position.java new file mode 100644 index 000000000000..cdfc4c2ff86c --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Position.java @@ -0,0 +1,19 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class Position { + private final int line; + private final int character; + + public Position(int line, int character) { + this.line = line; + this.character = character; + } + + public int getLine() { + return line; + } + + public int getCharacter() { + return character; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/PositionRange.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/PositionRange.java new file mode 100644 index 000000000000..8d216a636e16 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/PositionRange.java @@ -0,0 +1,19 @@ +package com.tabbyml.tabby4eclipse.chat; + +public class PositionRange extends Range { + private final Position start; + private final Position end; + + public PositionRange(Position start, Position end) { + this.start = start; + this.end = end; + } + + public Position getStart() { + return start; + } + + public Position getEnd() { + return end; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Range.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Range.java new file mode 100644 index 000000000000..5abc6534fd45 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Range.java @@ -0,0 +1,4 @@ +package com.tabbyml.tabby4eclipse.chat; + +public abstract class Range { +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java index bfb345699057..611fbe78790f 100644 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java @@ -66,7 +66,7 @@ public GitRepository getRepository(GitRepositoryParams params) { return null; } } catch (Exception e) { - logger.warn("Failed to get repository for: " + params.getUri(), e); + logger.debug("Failed to get repository for: " + params.getUri()); return null; } } @@ -92,7 +92,7 @@ public GitDiffResult getDiff(GitDiffParams params) { return null; } } catch (Exception e) { - logger.warn("Failed to get diff for: " + params.getRepository(), e); + logger.debug("Failed to get diff for: " + params.getRepository()); return null; } }