From 74c78dd91a95857a827cb36e77a439cb4f21372b Mon Sep 17 00:00:00 2001 From: Zhiming Ma Date: Sat, 24 Aug 2024 02:08:46 +0800 Subject: [PATCH] feat(eclipse): support to collect additional context used for code completion. (#2971) * feat(eclipse): impl git provider lsp client. * feat(eclipse): add client side language support based on lsp4e. * chore(eclipse): bump build number. --- clients/eclipse/feature/feature.xml | 4 +- clients/eclipse/plugin/META-INF/MANIFEST.MF | 2 +- .../tabbyml/tabby4eclipse/chat/ChatView.java | 9 +- .../tabby4eclipse/git/GitProvider.java | 45 +++++++++- .../tabby4eclipse/lsp/ConnectionProvider.java | 3 + .../tabby4eclipse/lsp/LanguageClientImpl.java | 74 +++++++++++++++- .../lsp/LanguageSupportProvider.java | 87 +++++++++++++++++++ .../lsp/protocol/GitDiffParams.java | 35 ++++++++ .../lsp/protocol/GitDiffResult.java | 20 +++++ .../lsp/protocol/GitRepositoryParams.java | 17 ++++ .../lsp/protocol/ReadFileParams.java | 37 ++++++++ .../lsp/protocol/ReadFileResult.java | 20 +++++ .../protocol/SemanticTokensRangeResult.java | 28 ++++++ 13 files changed, 371 insertions(+), 10 deletions(-) create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageSupportProvider.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffParams.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffResult.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitRepositoryParams.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileParams.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileResult.java create mode 100644 clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/SemanticTokensRangeResult.java diff --git a/clients/eclipse/feature/feature.xml b/clients/eclipse/feature/feature.xml index 05573d903f07..04749d707d6f 100644 --- a/clients/eclipse/feature/feature.xml +++ b/clients/eclipse/feature/feature.xml @@ -2,7 +2,7 @@ @@ -19,6 +19,6 @@ + version="0.0.1.14"/> diff --git a/clients/eclipse/plugin/META-INF/MANIFEST.MF b/clients/eclipse/plugin/META-INF/MANIFEST.MF index d4a9b9e3e33c..962ee183b327 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.1.13 +Bundle-Version: 0.0.1.14 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/ChatView.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java index 78bb0abcfda0..026f33f9eb5b 100644 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java @@ -52,6 +52,7 @@ import com.tabbyml.tabby4eclipse.lsp.StatusInfoHolder; import com.tabbyml.tabby4eclipse.lsp.protocol.Config; import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepositoryParams; import com.tabbyml.tabby4eclipse.lsp.protocol.ILanguageServer; import com.tabbyml.tabby4eclipse.lsp.protocol.IStatusService; import com.tabbyml.tabby4eclipse.lsp.protocol.StatusInfo; @@ -234,8 +235,9 @@ private void reloadContentForStatus(String status, boolean force) { } else { // Load main Config.ServerConfig config = serverConfigHolder.getConfig().getServer(); - if (force || currentConfig == null || currentConfig.getEndpoint() != config.getEndpoint() - || currentConfig.getToken() != config.getToken()) { + if (config != null + && (force || currentConfig == null || currentConfig.getEndpoint() != config.getEndpoint() + || currentConfig.getToken() != config.getToken())) { showMessage("Connecting to Tabby server..."); showChatPanel(false); currentConfig = config; @@ -504,7 +506,8 @@ private FileContext getActiveContext() { IFile file = ResourceUtil.getFile(activeTextEditor.getEditorInput()); URI fileUri = file.getLocationURI(); if (file != null) { - GitRepository gitInfo = GitProvider.getRepository(fileUri); + GitRepository gitInfo = GitProvider.getInstance() + .getRepository(new GitRepositoryParams(fileUri.toString())); IProject project = file.getProject(); if (gitInfo != null) { try { diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/GitProvider.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/GitProvider.java index f84ecd24a020..d00cd2a4b788 100644 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/GitProvider.java +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/GitProvider.java @@ -1,21 +1,35 @@ package com.tabbyml.tabby4eclipse.git; +import java.io.ByteArrayOutputStream; import java.io.File; import java.net.URI; import java.util.List; +import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.RemoteConfig; import com.tabbyml.tabby4eclipse.Logger; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitDiffParams; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitDiffResult; import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepositoryParams; public class GitProvider { - private static Logger logger = new Logger("GitProvider"); + public static GitProvider getInstance() { + return LazyHolder.INSTANCE; + } + + private static class LazyHolder { + private static final GitProvider INSTANCE = new GitProvider(); + } - public static GitRepository getRepository(URI uri) { + private Logger logger = new Logger("GitProvider"); + + public GitRepository getRepository(GitRepositoryParams params) { try { + URI uri = new URI(params.getUri()); File file = new File(uri); FileRepositoryBuilder builder = new FileRepositoryBuilder(); Repository repository = builder.findGitDir(file).build(); @@ -48,7 +62,32 @@ public static GitRepository getRepository(URI uri) { return null; } } catch (Exception e) { - logger.info("Failed to get repository for: " + uri); + logger.error("Failed to get repository for: " + params.getUri(), e); + return null; + } + } + + public GitDiffResult getDiff(GitDiffParams params) { + try { + URI repoPath = new URI(params.getRepository()); + File repoDir = new File(repoPath); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + Repository repository = builder.findGitDir(repoDir).build(); + if (repository != null) { + try (Git git = new Git(repository)) { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + git.diff().setCached(params.getCached()).setOutputStream(outStream).call(); + String diff = outStream.toString("UTF-8"); + + GitDiffResult result = new GitDiffResult(diff); + outStream.close(); + return result; + } + } else { + return null; + } + } catch (Exception e) { + logger.error("Failed to get diff for: " + params.getRepository(), e); return null; } } diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/ConnectionProvider.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/ConnectionProvider.java index 4d51cb59c091..25aafb22175b 100644 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/ConnectionProvider.java +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/ConnectionProvider.java @@ -112,6 +112,9 @@ private ClientCapabilities getClientCapabilities() { TabbyClientCapabilities tabbyClientCapabilities = new TabbyClientCapabilities(); tabbyClientCapabilities.setConfigDidChangeListener(true); tabbyClientCapabilities.setStatusDidChangeListener(true); + tabbyClientCapabilities.setWorkspaceFileSystem(true); + tabbyClientCapabilities.setGitProvider(true); + tabbyClientCapabilities.setLanguageSupport(true); ClientCapabilities clientCapabilities = new ClientCapabilities(); clientCapabilities.setTextDocument(textDocumentClientCapabilities); diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageClientImpl.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageClientImpl.java index bed7304e1fee..00afb3e2e05c 100644 --- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageClientImpl.java +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageClientImpl.java @@ -1,10 +1,30 @@ package com.tabbyml.tabby4eclipse.lsp; +import java.net.URI; +import java.util.List; import java.util.concurrent.CompletableFuture; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SemanticTokensRangeParams; +import org.eclipse.jface.text.IDocument; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServers; +import org.eclipse.lsp4j.DeclarationParams; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import com.tabbyml.tabby4eclipse.git.GitProvider; import com.tabbyml.tabby4eclipse.lsp.protocol.Config; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitDiffParams; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitDiffResult; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository; +import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepositoryParams; +import com.tabbyml.tabby4eclipse.lsp.protocol.ReadFileParams; +import com.tabbyml.tabby4eclipse.lsp.protocol.ReadFileResult; +import com.tabbyml.tabby4eclipse.lsp.protocol.SemanticTokensRangeResult; import com.tabbyml.tabby4eclipse.lsp.protocol.StatusInfo; public class LanguageClientImpl extends org.eclipse.lsp4e.LanguageClientImpl { @@ -12,9 +32,61 @@ public class LanguageClientImpl extends org.eclipse.lsp4e.LanguageClientImpl { void statusDidChange(StatusInfo params) { StatusInfoHolder.getInstance().setStatusInfo(params); } - + @JsonNotification("tabby/config/didChange") void statusDidChange(Config params) { ServerConfigHolder.getInstance().setConfig(params); } + + @JsonRequest("tabby/workspaceFileSystem/readFile") + CompletableFuture workspaceReadFile(ReadFileParams params) { + if (params.getFormat().equals("text")) { + try { + URI uri = new URI(params.getUri()); + IDocument document = LSPEclipseUtils.getDocument(uri); + Range range = params.getRange(); + + ReadFileResult result = new ReadFileResult(); + if (range != null) { + int start = LSPEclipseUtils.toOffset(range.getStart(), document); + int end = LSPEclipseUtils.toOffset(range.getEnd(), document); + result.setText(document.get(start, end - start)); + } else { + result.setText(document.get()); + } + return CompletableFuture.completedFuture(result); + } catch (Exception e) { + return CompletableFuture.completedFuture(null); + } + } else { + return CompletableFuture.completedFuture(null); + } + } + + @JsonRequest("tabby/git/repository") + CompletableFuture gitRepository(GitRepositoryParams params) { + CompletableFuture future = new CompletableFuture<>(); + GitRepository result = GitProvider.getInstance().getRepository(params); + future.complete(result); + return future; + } + + @JsonRequest("tabby/git/diff") + CompletableFuture gitDiff(GitDiffParams params) { + CompletableFuture future = new CompletableFuture<>(); + GitDiffResult result = GitProvider.getInstance().getDiff(params); + future.complete(result); + return future; + } + + @JsonRequest("tabby/languageSupport/textDocument/declaration") + CompletableFuture, List>> languageSupportDeclaration( + DeclarationParams params) { + return LanguageSupportProvider.getInstance().languageSupportDeclaration(params); + } + + @JsonRequest("tabby/languageSupport/textDocument/semanticTokens/range") + CompletableFuture languageSupportSemanticTokensRange(SemanticTokensRangeParams params) { + return LanguageSupportProvider.getInstance().languageSupportSemanticTokensRange(params); + } } diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageSupportProvider.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageSupportProvider.java new file mode 100644 index 000000000000..20e3aed6f961 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/LanguageSupportProvider.java @@ -0,0 +1,87 @@ +package com.tabbyml.tabby4eclipse.lsp; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.net.URI; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServers; +import org.eclipse.lsp4j.DeclarationParams; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.eclipse.lsp4j.SemanticTokensRangeParams; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +import com.tabbyml.tabby4eclipse.Logger; +import com.tabbyml.tabby4eclipse.lsp.protocol.SemanticTokensRangeResult; + +public class LanguageSupportProvider { + public static LanguageSupportProvider getInstance() { + return LazyHolder.INSTANCE; + } + + private static class LazyHolder { + private static final LanguageSupportProvider INSTANCE = new LanguageSupportProvider(); + } + + private Logger logger = new Logger("LanguageSupportProvider"); + + CompletableFuture, List>> languageSupportDeclaration( + DeclarationParams params) { + try { + URI uri = new URI(params.getTextDocument().getUri()); + IDocument document = LSPEclipseUtils.getDocument(uri); + + CompletableFuture, List>> future = LanguageServers + .forDocument(document).withFilter((serverCapabilities) -> { + return serverCapabilities.getDeclarationProvider() != null; + }).computeFirst((wrapper, server) -> { + return server.getTextDocumentService().declaration(params); + }).thenApply((result) -> { + if (result.isPresent()) { + return result.get(); + } else { + return null; + } + }); + return future; + } catch (Exception e) { + logger.error("Error while processing languageSupport/declaration.", e); + return CompletableFuture.completedFuture(null); + } + } + + CompletableFuture languageSupportSemanticTokensRange(SemanticTokensRangeParams params) { + try { + URI uri = new URI(params.getTextDocument().getUri()); + IDocument document = LSPEclipseUtils.getDocument(uri); + + CompletableFuture future = LanguageServers.forDocument(document) + .withFilter((serverCapabilities) -> { + return serverCapabilities.getSelectionRangeProvider() != null; + }).computeFirst((wrapper, server) -> { + return server.getTextDocumentService().semanticTokensRange(params).thenApply((tokens) -> { + SemanticTokensRangeResult result = new SemanticTokensRangeResult(); + SemanticTokensLegend legend = wrapper.getServerCapabilities().getSemanticTokensProvider() + .getLegend(); + result.setLegend(legend); + result.setTokens(tokens); + return result; + }); + }).thenApply((result) -> { + if (result.isPresent()) { + return result.get(); + } else { + return null; + } + }); + return future; + } catch (Exception e) { + logger.error("Error while processing languageSupport/semanticTokens/range.", e); + return CompletableFuture.completedFuture(null); + } + } + +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffParams.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffParams.java new file mode 100644 index 000000000000..7c116bd3391b --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffParams.java @@ -0,0 +1,35 @@ +package com.tabbyml.tabby4eclipse.lsp.protocol; + +public class GitDiffParams { + private String repository; + private boolean cached; + + public GitDiffParams() { + } + + public GitDiffParams(String repository) { + this.repository = repository; + this.cached = false; + } + + public GitDiffParams(String repository, boolean cached) { + this.repository = repository; + this.cached = cached; + } + + public String getRepository() { + return repository; + } + + public void setRepository(String repository) { + this.repository = repository; + } + + public boolean getCached() { + return cached; + } + + public void setCached(boolean cached) { + this.cached = cached; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffResult.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffResult.java new file mode 100644 index 000000000000..1de2a9d4202e --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitDiffResult.java @@ -0,0 +1,20 @@ +package com.tabbyml.tabby4eclipse.lsp.protocol; + +public class GitDiffResult { + private String diff; + + public GitDiffResult() { + } + + public GitDiffResult(String diff) { + this.diff = diff; + } + + public String getDiff() { + return diff; + } + + public void setDiff(String diff) { + this.diff = diff; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitRepositoryParams.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitRepositoryParams.java new file mode 100644 index 000000000000..806c8c60f745 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/GitRepositoryParams.java @@ -0,0 +1,17 @@ +package com.tabbyml.tabby4eclipse.lsp.protocol; + +public class GitRepositoryParams { + private String uri; + + public GitRepositoryParams(String uri) { + this.uri = uri; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileParams.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileParams.java new file mode 100644 index 000000000000..ec2f82f8151e --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileParams.java @@ -0,0 +1,37 @@ +package com.tabbyml.tabby4eclipse.lsp.protocol; + +import org.eclipse.lsp4j.Range; + +public class ReadFileParams { + private String uri; + private String format; + private Range range; + + public ReadFileParams(String uri) { + this.uri = uri; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public Range getRange() { + return range; + } + + public void setRange(Range range) { + this.range = range; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileResult.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileResult.java new file mode 100644 index 000000000000..586e40768b48 --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/ReadFileResult.java @@ -0,0 +1,20 @@ +package com.tabbyml.tabby4eclipse.lsp.protocol; + +public class ReadFileResult { + private String text; + + public ReadFileResult() { + } + + public ReadFileResult(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/SemanticTokensRangeResult.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/SemanticTokensRangeResult.java new file mode 100644 index 000000000000..7a4d302d4a0d --- /dev/null +++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/lsp/protocol/SemanticTokensRangeResult.java @@ -0,0 +1,28 @@ +package com.tabbyml.tabby4eclipse.lsp.protocol; + +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensLegend; + +public class SemanticTokensRangeResult { + private SemanticTokensLegend legend; + private SemanticTokens tokens; + + public SemanticTokensRangeResult() { + } + + public SemanticTokensLegend getLegend() { + return legend; + } + + public void setLegend(SemanticTokensLegend legend) { + this.legend = legend; + } + + public SemanticTokens getTokens() { + return tokens; + } + + public void setTokens(SemanticTokens tokens) { + this.tokens = tokens; + } +}