helix-editor / helix

A post-modern modal text editor.
https://helix-editor.com
Mozilla Public License 2.0
33.55k stars 2.49k forks source link

LSP - support attaching to paths within archives #11500

Open tknawara opened 2 months ago

tknawara commented 2 months ago

Hey,

From previous discussion around an issue with lsp go to definition, in turned out that for some languages (like JVM based languages ex: clojure). When the editor initiates a go-to-definition, the language server returns a path in the format zipfile://<actual path> the file could be a .jar file for example.

example log.txt for an interaction with clojure-lsp

2024-08-14T17:44:22.578 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{"general":{"positionEncodings":["utf-8","utf-32","utf-16"]},"textDocument":{"codeAction":{"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"dataSupport":true,"disabledSupport":true,"isPreferredSupport":true,"resolveSupport":{"properties":["edit","command"]}},"completion":{"completionItem":{"deprecatedSupport":true,"insertReplaceSupport":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits"]},"snippetSupport":true,"tagSupport":{"valueSet":[1]}},"completionItemKind":{}},"formatting":{"dynamicRegistration":false},"hover":{"contentFormat":["markdown"]},"inlayHint":{"dynamicRegistration":false},"publishDiagnostics":{"tagSupport":{"valueSet":[1,2]},"versionSupport":true},"rename":{"dynamicRegistration":false,"honorsChangeAnnotations":false,"prepareSupport":true},"signatureHelp":{"signatureInformation":{"activeParameterSupport":true,"documentationFormat":["markdown"],"parameterInformation":{"labelOffsetSupport":true}}}},"window":{"workDoneProgress":true},"workspace":{"applyEdit":true,"configuration":true,"didChangeConfiguration":{"dynamicRegistration":false},"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":false},"executeCommand":{"dynamicRegistration":false},"fileOperations":{"didRename":true,"willRename":true},"inlayHint":{"refreshSupport":false},"symbol":{"dynamicRegistration":false},"workspaceEdit":{"documentChanges":true,"failureHandling":"abort","normalizesLineEndings":false,"resourceOperations":["create","rename","delete"]},"workspaceFolders":true}},"clientInfo":{"name":"helix","version":"24.7 (079f5442)"},"processId":6150,"rootPath":"/home/tarek/workspace/clojure/ps","rootUri":"file:///home/tarek/workspace/clojure/ps","workspaceFolders":[{"name":"ps","uri":"file:///home/tarek/workspace/clojure/ps"}]},"id":0}
2024-08-14T17:44:23.131 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///home/tarek/workspace/clojure/ps/src/ps/core.clj","diagnostics":[{"range":{"start":{"line":6,"character":5},"end":{"line":6,"character":9}},"tags":[1],"message":"unused binding args","code":"unused-binding","langs":[],"severity":2,"source":"clj-kondo"},{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":26}},"tags":[],"message":"Unresolved var: hk-client/get","code":"unresolved-var","langs":[],"severity":2,"source":"clj-kondo"},{"range":{"start":{"line":10,"character":13},"end":{"line":10,"character":26}},"tags":[],"message":"Unresolved var: hk-client/get","code":"unresolved-var","langs":[],"severity":2,"source":"clj-kondo"},{"range":{"start":{"line":14,"character":6},"end":{"line":14,"character":19}},"tags":[1],"message":"Unused public var 'ps.core/error-message'","code":"clojure-lsp/unused-public-var","langs":[],"severity":3,"source":"clojure-lsp"}]}}
2024-08-14T17:44:23.133 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///home/tarek/workspace/clojure/ps/test/ps/core_test.clj","diagnostics":[{"range":{"start":{"line":1,"character":33},"end":{"line":1,"character":37}},"tags":[],"message":"use alias or :refer [deftest is testing]","code":"refer-all","langs":[],"severity":2,"source":"clj-kondo","data":{"refers":["deftest","is","testing"]}},{"range":{"start":{"line":2,"character":28},"end":{"line":2,"character":32}},"tags":[],"message":"use alias or :refer","code":"refer-all","langs":[],"severity":2,"source":"clj-kondo","data":{"refers":[]}}]}}
2024-08-14T17:44:23.133 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"documentSymbolProvider":true,"textDocumentSync":{"openClose":true,"change":1,"save":{"includeText":true}},"declarationProvider":true,"semanticTokensProvider":{"legend":{"tokenTypes":["namespace","type","function","macro","keyword","class","variable","method","event","interface"],"tokenModifiers":["definition","defaultLibrary","implementation"]},"range":true,"full":true},"linkedEditingRangeProvider":true,"workspaceSymbolProvider":true,"experimental":{"testTree":true,"projectTree":true,"cursorInfo":true,"serverInfo":true,"clojuredocs":true},"codeActionProvider":{"codeActionKinds":["quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]},"documentHighlightProvider":true,"completionProvider":{"resolveProvider":true,"triggerCharacters":[":","/"]},"workspace":{"fileOperations":{"willRename":{"filters":[{"scheme":"file","pattern":{"glob":"**/*.{clj,cljs,cljc,cljd,edn,bb,clj_kondo}","matches":"file"}}]},"didRename":{"filters":[{"scheme":"file","pattern":{"glob":"**/*.{clj,cljs,cljc,cljd,edn,bb,clj_kondo}","matches":"file"}}]}}},"implementationProvider":true,"signatureHelpProvider":{"triggerCharacters":[]},"documentRangeFormattingProvider":true,"executeCommandProvider":{"commands":["add-import-to-namespace","add-missing-import","add-missing-libspec","add-require-suggestion","backward-barf","backward-slurp","change-coll","clean-ns","create-function","create-test","cursor-info","cycle-coll","cycle-keyword-auto-resolve","cycle-privacy","demote-fn","destructure-keys","drag-backward","drag-forward","drag-param-backward","drag-param-forward","expand-let","extract-function","extract-to-def","forward-barf","forward-slurp","get-in-all","get-in-less","get-in-more","get-in-none","inline-symbol","introduce-let","kill-sexp","move-coll-entry-down","move-coll-entry-up","move-form","move-to-let","promote-fn","raise-sexp","replace-refer-all-with-alias","replace-refer-all-with-refer","resolve-macro-as","restructure-keys","server-info","sort-clauses","sort-map","suppress-diagnostic","thread-first","thread-first-all","thread-last","thread-last-all","unwind-all","unwind-thread"]},"referencesProvider":true,"codeLensProvider":{"resolveProvider":true},"foldingRangeProvider":true,"callHierarchyProvider":true,"documentFormattingProvider":true,"renameProvider":{"prepareProvider":true},"definitionProvider":true,"hoverProvider":true}}}
2024-08-14T17:44:23.133 helix_lsp::transport [INFO] clojure-lsp <- {"capabilities":{"callHierarchyProvider":true,"codeActionProvider":{"codeActionKinds":["quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]},"codeLensProvider":{"resolveProvider":true},"completionProvider":{"resolveProvider":true,"triggerCharacters":[":","/"]},"declarationProvider":true,"definitionProvider":true,"documentFormattingProvider":true,"documentHighlightProvider":true,"documentRangeFormattingProvider":true,"documentSymbolProvider":true,"executeCommandProvider":{"commands":["add-import-to-namespace","add-missing-import","add-missing-libspec","add-require-suggestion","backward-barf","backward-slurp","change-coll","clean-ns","create-function","create-test","cursor-info","cycle-coll","cycle-keyword-auto-resolve","cycle-privacy","demote-fn","destructure-keys","drag-backward","drag-forward","drag-param-backward","drag-param-forward","expand-let","extract-function","extract-to-def","forward-barf","forward-slurp","get-in-all","get-in-less","get-in-more","get-in-none","inline-symbol","introduce-let","kill-sexp","move-coll-entry-down","move-coll-entry-up","move-form","move-to-let","promote-fn","raise-sexp","replace-refer-all-with-alias","replace-refer-all-with-refer","resolve-macro-as","restructure-keys","server-info","sort-clauses","sort-map","suppress-diagnostic","thread-first","thread-first-all","thread-last","thread-last-all","unwind-all","unwind-thread"]},"experimental":{"clojuredocs":true,"cursorInfo":true,"projectTree":true,"serverInfo":true,"testTree":true},"foldingRangeProvider":true,"hoverProvider":true,"implementationProvider":true,"linkedEditingRangeProvider":true,"referencesProvider":true,"renameProvider":{"prepareProvider":true},"semanticTokensProvider":{"full":true,"legend":{"tokenModifiers":["definition","defaultLibrary","implementation"],"tokenTypes":["namespace","type","function","macro","keyword","class","variable","method","event","interface"]},"range":true},"signatureHelpProvider":{"triggerCharacters":[]},"textDocumentSync":{"change":1,"openClose":true,"save":{"includeText":true}},"workspace":{"fileOperations":{"didRename":{"filters":[{"pattern":{"glob":"**/*.{clj,cljs,cljc,cljd,edn,bb,clj_kondo}","matches":"file"},"scheme":"file"}]},"willRename":{"filters":[{"pattern":{"glob":"**/*.{clj,cljs,cljc,cljd,edn,bb,clj_kondo}","matches":"file"},"scheme":"file"}]}}},"workspaceSymbolProvider":true}}
2024-08-14T17:44:23.133 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","method":"initialized","params":{}}
2024-08-14T17:44:23.134 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"clojure","text":"(ns ps.core\n  (:gen-class)\n  (:require [org.httpkit.client :as hk-client]))\n\n(defn -main\n  \"I don't do a whole lot ... yet.\"\n  [& args]\n  (println \"Hello, World!\"))\n\n(let [resp1 (hk-client/get \"http://http-kit.org/\")\n      resp2 (hk-client/get \"http://clojure.org/\")]\n  (println \"Response 1's status: \" (:status @resp1))\n  (println \"Response 2's status: \" (:status @resp2)))\n\n(defn error-message\n  [severity]\n  (str \"OH GOD! IT'S A DISASTER! WE'RE \"\n       (if (= severity :mild)\n         \"MILDLY INCONVENIENCED!\"\n         \"DOOOOOOOOMED!\")))\n","uri":"file:///home/tarek/workspace/clojure/ps/src/ps/core.clj","version":0}}}
2024-08-14T17:44:23.136 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","method":"client/registerCapability","params":{"registrations":[{"id":"id","method":"workspace/didChangeWatchedFiles","registerOptions":{"watchers":[{"globPattern":"**/*.{clj,cljs,cljc,cljd,edn,bb,clj_kondo}"}]}}]},"id":1}
2024-08-14T17:44:23.136 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","result":null,"id":1}
2024-08-14T17:44:23.204 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///home/tarek/workspace/clojure/ps/src/ps/core.clj","diagnostics":[{"range":{"start":{"line":6,"character":5},"end":{"line":6,"character":9}},"tags":[1],"message":"unused binding args","code":"unused-binding","langs":[],"severity":2,"source":"clj-kondo"},{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":26}},"tags":[],"message":"Unresolved var: hk-client/get","code":"unresolved-var","langs":[],"severity":2,"source":"clj-kondo"},{"range":{"start":{"line":10,"character":13},"end":{"line":10,"character":26}},"tags":[],"message":"Unresolved var: hk-client/get","code":"unresolved-var","langs":[],"severity":2,"source":"clj-kondo"},{"range":{"start":{"line":14,"character":6},"end":{"line":14,"character":19}},"tags":[1],"message":"Unused public var 'ps.core/error-message'","code":"clojure-lsp/unused-public-var","langs":[],"severity":3,"source":"clojure-lsp"}]}}
2024-08-14T17:44:27.107 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","method":"textDocument/definition","params":{"position":{"character":24,"line":9},"textDocument":{"uri":"file:///home/tarek/workspace/clojure/ps/src/ps/core.clj"}},"id":1}
2024-08-14T17:44:27.113 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","id":1,"result":{"uri":"zipfile:///home/tarek/.m2/repository/http-kit/http-kit/2.8.0/http-kit-2.8.0.jar::org/httpkit/client.clj","range":{"start":{"line":0,"character":4},"end":{"line":0,"character":22}}}}
2024-08-14T17:44:27.113 helix_lsp::transport [INFO] clojure-lsp <- {"range":{"end":{"character":22,"line":0},"start":{"character":4,"line":0}},"uri":"zipfile:///home/tarek/.m2/repository/http-kit/http-kit/2.8.0/http-kit-2.8.0.jar::org/httpkit/client.clj"}
2024-08-14T17:44:27.113 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"clojure","text":"\n","uri":"file:///home/tarek/.m2/repository/http-kit/http-kit/2.8.0/http-kit-2.8.0.jar::org/httpkit/client.clj","version":0}}}
2024-08-14T17:44:27.168 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///home/tarek/.m2/repository/http-kit/http-kit/2.8.0/http-kit-2.8.0.jar::org/httpkit/client.clj","diagnostics":[]}}
2024-08-14T17:44:30.068 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","method":"shutdown","id":2}
2024-08-14T17:44:30.069 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","id":2,"result":null}
2024-08-14T17:44:30.069 helix_lsp::transport [INFO] clojure-lsp <- null
2024-08-14T17:44:30.069 helix_lsp::transport [INFO] clojure-lsp -> {"jsonrpc":"2.0","method":"exit"}

This is the path given by the server

2024-08-14T17:44:27.113 helix_lsp::transport [INFO] clojure-lsp <- {"jsonrpc":"2.0","id":1,"result":{"uri":"zipfile:///home/tarek/.m2/repository/http-kit/http-kit/2.8.0/http-kit-2.8.0.jar::org/httpkit/client.clj","range":{"start":{"line":0,"character":4},"end":{"line":0,"character":22}}}}

and lsp implementation

atm, helix doesn't support attaching to these paths, the current behavior is to show an empty buffer, but there is a fix to show proper error messages for when the editor can't attach to the path given by the lsp.

Having proper errors in place is great, but it would be even better to support this use case, here are similar efforts done by other editors, example neovim.

kirawi commented 2 months ago

It doesn't sound to me like this is part of the LSP standard, so this would likely have to be implemented as a plugin.

the-mikedavis commented 2 months ago

This is not quite an extension to the LSP spec: the spec allows sending any kind of URI and the zipfile://path-to-file.zip::path-within-zip URI does seem to be valid and has some use by other editors. Whether opening files within zipfiles is in scope for core I'm not sure and don't have a strong opinion on.

pascalkuthe commented 2 months ago

I think this is kne of the "non-standardized" clientside features. Same as client-side commands. They are technicallypart of the spec but it's just a blackboard string and the implementation is up to the server/client. So it feels like this is out of scope for similar reasons that client-side commands are out of scope.