[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/haskell-tng-mode e73bc19 335/385: jump-to-definition
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/haskell-tng-mode e73bc19 335/385: jump-to-definition |
Date: |
Tue, 5 Oct 2021 23:59:59 -0400 (EDT) |
branch: elpa/haskell-tng-mode
commit e73bc19ffd61a3b829d06bfcf35b9b292087d82c
Author: Tseen She <ts33n.sh3@gmail.com>
Commit: Tseen She <ts33n.sh3@gmail.com>
jump-to-definition
---
README.md | 11 +++-
haskell-tng-extra-projectile.el | 1 +
haskell-tng-extra.el | 2 +-
haskell-tng-hsinspect.el | 102 ++++++++++++++++++++++++++----
haskell-tng-util.el | 4 +-
screencasts/jump-to-definition.gif | Bin 0 -> 43475 bytes
screencasts/jump-to-definition.mp4 | Bin 0 -> 218087 bytes
test/data/hsinspect-0.0.7-imports.sexp.gz | Bin 364 -> 0 bytes
test/data/hsinspect-0.0.7-index.sexp.gz | Bin 52239 -> 0 bytes
test/data/hsinspect-0.0.8-index.sexp.gz | Bin 74636 -> 0 bytes
test/data/hsinspect-0.0.9-index.sexp.gz | Bin 0 -> 73914 bytes
test/haskell-tng-hsinspect-test.el | 24 ++++---
12 files changed, 114 insertions(+), 30 deletions(-)
diff --git a/README.md b/README.md
index c8fb8fc..e051fb2 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ The optional command line tool
[`hsinspect`](https://gitlab.com/tseenshe/hsinspe
To use this feature you must install `hsinspect` command line tool and the
`ghcflags` plugin to every `.cabal` file:
-1. `build-tool-depends: hsinspect:hsinspect == 0.0.8` (or make `hsinspect`
available globally, self-managing `ghc` versions)
+1. `build-tool-depends: hsinspect:hsinspect == 0.0.9` (or make `hsinspect`
available globally, self-managing `ghc` versions)
2. `build-depends: ghcflags == 1.0.2`
3. add `ghc-options: -fplugin GhcFlags.Plugin`
@@ -79,6 +79,7 @@ recordmydesktop --no-sound --delay 3
ffmpeg -i out.ogv -vf crop=500:300:5:0 fqn-at-point-completion.mp4
ffmpeg -i out.ogv -vf crop=500:300:5:0,scale=300:-1 -hide_banner
fqn-at-point-completion.gif
+
gitlab markdown allows embedded mp4s but it makes them huge, so use gifs
-->
@@ -88,6 +89,10 @@ To automatically import a symbol at point, use `M-x
haskell-tng-import-symbol-at
![screencast of
haskell-tng-import-symbol-at-point](screencasts/import-symbol-at-point.gif)
+To jump to the definition of a symbol defined outside the project, use `M-x
haskell-tng-jump-to-definition`.
+
+![screencast of
haskell-tng-jump-to-definition](screencasts/jump-to-definition.gif)
+
All `hsinspect` commands are heavily cached and never invalidated to maximise
availability. If the caches are out of date and no longer useful, run the
command again prefixed with `C-u` to force a fresh invocaton of hsinspect.
## Extras
@@ -103,6 +108,8 @@ Third party Haskell tools must be installed separately
(e.g. via `cabal v2-insta
- [`ormolu`](https://github.com/tweag/ormolu)
- `C-c p R` invoke
[`fast-tags`](https://hackage.haskell.org/package/fast-tags) via
[`projectile`](https://github.com/bbatsov/projectile)
+Note that to jump to definition inside the project, use
`projectile-regenerate-tags` and `projectile-find-tag`.
+
## Contributing
Bug reports and feature requests are a source of anxiety for me, and encourage
an unhealthy customer / supplier relationship between users and contributors.
@@ -137,7 +144,6 @@ This is the status of core features:
- `lsp-mode` /
[`haskell-ide-engine`](https://github.com/haskell/haskell-ide-engine) for more
advanced IDE features.
- Imports
- - quick manual add `import`
- company-mode backend specific to import sections that detect context,
powered by local hoogle cli
- expand import list into explicit list (perhaps via `:browse` but better as
standalone tool) for symbol-at-point (assuming no shadowing).
- convert wildcard import to explicit list
@@ -146,7 +152,6 @@ This is the status of core features:
- visual indicator of what has been exported (hsinspect could do this for
compilable code)
- Hoogle integration
- build local hoogle database for a project
- - local cli jump-to-source of symbol-at-point / type-at-point (i.e. explicit
fully qualified name)
- local cli search
- local / remote search with doc in browser
- `.cabal` editing / navigation
diff --git a/haskell-tng-extra-projectile.el b/haskell-tng-extra-projectile.el
index d3d28cc..1beac1c 100644
--- a/haskell-tng-extra-projectile.el
+++ b/haskell-tng-extra-projectile.el
@@ -11,6 +11,7 @@
;; TODO fix the haskell-stack detection to also include cabal
;; TODO populate the projectile compile/run/test commands
+;; TODO haskell-tng-jump-to-definition-fallback
(make-variable-buffer-local 'projectile-tags-command)
(add-hook
diff --git a/haskell-tng-extra.el b/haskell-tng-extra.el
index 7eb1353..c6f0dbb 100644
--- a/haskell-tng-extra.el
+++ b/haskell-tng-extra.el
@@ -115,7 +115,7 @@ When in a comment and called with a prefix, the comment
will be completed."
"Adds an unqualified wildcard import."
;; TODO autocomplete on available imports
(interactive "s")
- (haskell-tng--import-symbol module nil nil))
+ (haskell-tng--util-import-symbol module nil nil))
;;;###autoload
(defun haskell-tng-current-module ()
diff --git a/haskell-tng-hsinspect.el b/haskell-tng-hsinspect.el
index 10a95bb..37fbf09 100644
--- a/haskell-tng-hsinspect.el
+++ b/haskell-tng-hsinspect.el
@@ -44,7 +44,86 @@ A prefix argument ensures that caches are flushes."
(popup-tip (format "%s" found)))
(user-error "Not found"))
-;; TODO jump-to-definition using import + index + heuristics
+;;;###autoload
+(defun haskell-tng-jump-to-definition (&optional alt)
+ "Consult the `imports' in scope to calculate the symbol at point,
+then find the package using the `index', then visit the
+definition of the symbol in the build tool's source archive.
+
+TODO: support local / git packages by consulting `plan.json'"
+ (interactive "P")
+ ;; TODO better error reporting when any of these things fail
+ (when-let* ((imports (haskell-tng--hsinspect-imports nil alt))
+ (index (haskell-tng--hsinspect-index alt))
+ ;; TODO imports and index can be calculated in parallel
+ (sym (haskell-tng--hsinspect-symbol-at-point))
+ (found (haskell-tng--hsinspect-qualify imports sym))
+ (parts (haskell-tng--string-split-last found "."))
+ (module (car parts))
+ (name (cdr parts))
+ (srcid (haskell-tng--hsinspect-find-srcid index module))
+ (tarball (haskell-tng--hsinspect-srcid-source srcid))
+ (file (concat
+ ;; TODO string-replace would be nice...
+ (mapconcat 'identity (split-string module (rx ".")) "/" )
+ ".hs")))
+ (when (not (file-exists-p tarball))
+ ;; NOTE we can't do this with stack because it doesn't have the
equivalent
+ ;; of the "get" command. Also, it is not clear where stack puts source
+ ;; code, so no point looking.
+ ;;
+ ;; WORKAROUND https://github.com/haskell/cabal/issues/6443
+ (shell-command (format "cabal get %s -d /var/empty &" srcid))
+ (error "%s was not found, attempting to download: please try again
later" tarball))
+
+ (message "Loading %s from %s" sym tarball)
+ ;; TODO follow re-exports
+ (find-file tarball)
+ (let ((archive (current-buffer)))
+ (goto-char (point-min))
+ (re-search-forward (rx-to-string `(: (* any) ,file)))
+ (tar-extract)
+ (kill-buffer archive)
+ (read-only-mode 1)
+ (goto-char (point-min))
+ ;; TODO re-use the imenu top-level parser
+ ;; avoid false positives in export lists
+ (re-search-forward (rx line-start "import" word-end) nil t)
+ ;; will unfortunately find first uses
+ (or
+ (re-search-forward (rx-to-string `(: (| bol "| " "data " "type " "class
") ,name symbol-end)))
+ (re-search-forward (rx-to-string `(: symbol-start ,name
symbol-end)))))))
+
+(defun haskell-tng--string-split-last (str sep)
+ "Return `(front . back)' of a STR split on the last SEP."
+ ;; TODO optimise
+ (let* ((parts (split-string str (regexp-quote sep)))
+ (front (mapconcat 'identity (butlast parts) sep))
+ (back (car (last parts))))
+ (cons front back)))
+
+(defun haskell-tng--hsinspect-srcid-source (srcid)
+ (let* ((parts (haskell-tng--string-split-last srcid "-"))
+ (package (car parts))
+ (version (cdr parts)))
+ (expand-file-name
+ (concat "~/.cabal/packages/hackage.haskell.org/" package "/" version "/"
srcid ".tar.gz"))))
+
+;; TODO expose the inplace information instead of filtering
+(defun haskell-tng--hsinspect-find-srcid (index module)
+ ;; requires 0.0.9+
+ (alist-get
+ 'srcid
+ (seq-find
+ (lambda (pkg-entry)
+ (when (not (alist-get 'inplace pkg-entry))
+ (seq-find
+ (lambda (module-entry)
+ (equal module (alist-get 'module module-entry)))
+ (alist-get 'modules pkg-entry))))
+ index)))
+
+;; TODO haskell-tng-show-documentation
(defvar-local haskell-tng-hsinspect-as
;; TODO populate with even more than this
@@ -82,8 +161,8 @@ Respects the `C-u' cache invalidation convention."
;; TODO add parens around operators (or should that be in the utility?)
(let (qual
(flush-cache (and alt (not (eq '- alt)))))
- (when-let* ((index (haskell-tng--hsinspect-index flush-cache))
- (sym (haskell-tng--hsinspect-symbol-at-point)))
+ (when-let ((index (haskell-tng--hsinspect-index flush-cache))
+ (sym (haskell-tng--hsinspect-symbol-at-point)))
(message "Seaching for '%s' in %s modules" sym (length index))
(when (string-match (rx bos (group (+ anything)) "." (group (+ (not (any
".")))) eos) sym)
@@ -135,7 +214,7 @@ Respects the `C-u' cache invalidation convention."
(let* ((name (alist-get 'name entry))
(type (alist-get 'type entry))
(id (pcase (alist-get 'class entry)
- ((or 'id 'con) name)
+ ((or 'id 'con 'pat) name)
('tycon type)))
(full (concat module "." id)))
(if as
@@ -180,13 +259,14 @@ Respects the `C-u' cache invalidation convention."
;; entries to the user. We should dedupe that to just the cons unless we have a
;; way to make the choice clearer.
(defun haskell-tng--hsinspect-import-candidates (index sym)
- "Return an list of alists with keys: unitid, module, name, type.
-When using hsinspect-0.0.8, also: class, export, flavour."
+ "Return an list of alists with keys: module, name, type.
+When using hsinspect-0.0.8, also: class, export, flavour.
+When using hsinspect-0.0.9, also: srcid."
;; TODO threading/do syntax
;; TODO alist variable binding like RecordWildcards
(seq-mapcat
(lambda (pkg-entry)
- (let ((unitid (alist-get 'unitid pkg-entry))
+ (let ((srcid (alist-get 'srcid pkg-entry))
(modules (alist-get 'modules pkg-entry)))
(seq-mapcat
(lambda (module-entry)
@@ -200,7 +280,7 @@ When using hsinspect-0.0.8, also: class, export, flavour."
(export (alist-get 'export entry))
(flavour (alist-get 'flavour entry)))
(when (or (equal name sym) (equal type sym))
- `(((unitid . ,unitid)
+ `(((srcid . ,srcid)
(module . ,module)
(name . ,name)
(type . ,type)
@@ -246,7 +326,7 @@ When using hsinspect-0.0.8, also: class, export, flavour."
(defvar-local haskell-tng--hsinspect-imports nil)
(defun haskell-tng--hsinspect-imports (&optional no-work flush-cache)
- (haskell-tng--hsinspect-cached
+ (haskell-tng--util-cached
(lambda () (haskell-tng--hsinspect flush-cache "imports" buffer-file-name))
'haskell-tng--hsinspect-imports
(concat "hsinspect-0.0.7" buffer-file-name "." "imports")
@@ -257,7 +337,7 @@ When using hsinspect-0.0.8, also: class, export, flavour."
"Add the import to the current buffer and update
`haskell-tng--hsinspect-imports'.
Does not persist the cache changes to disk."
- (haskell-tng--import-symbol module as sym)
+ (haskell-tng--util-import-symbol module as sym)
(let ((updates (haskell-tng--hsinspect-extract-imports index module as sym)))
(setq haskell-tng--hsinspect-imports
(append haskell-tng--hsinspect-imports updates))))
@@ -294,7 +374,7 @@ Does not persist the cache changes to disk."
"Finds and checks the hsinspect binary for the current buffer.
This is uncached, prefer `haskell-tng--hsinspect-exe'."
- (let ((supported '("0.0.7" "0.0.8" "0.0.9"))
+ (let ((supported '("0.0.7" "0.0.8" "0.0.9" "0.0.10"))
(bin
(car
(last
diff --git a/haskell-tng-util.el b/haskell-tng-util.el
index c43f0c8..32a591a 100644
--- a/haskell-tng-util.el
+++ b/haskell-tng-util.el
@@ -71,7 +71,7 @@ and taking a regexp."
(while (not (setq ,res ,test)) ,@body)
,res)))
-(defun haskell-tng--import-symbol (module &optional as sym)
+(defun haskell-tng--util-import-symbol (module &optional as sym)
"Adds an import for MODULE."
;; TODO outsource to `hsimport' when it does de-duping and formatting.
(save-excursion
@@ -96,7 +96,7 @@ and taking a regexp."
"\n")))
;; TODO needs a unit test
-(defun haskell-tng--hsinspect-cached
+(defun haskell-tng--util-cached
(work sym key &optional no-work reset)
"A two-tier (variable and disk-based) cache over WORK.
diff --git a/screencasts/jump-to-definition.gif
b/screencasts/jump-to-definition.gif
new file mode 100644
index 0000000..3997d7a
Binary files /dev/null and b/screencasts/jump-to-definition.gif differ
diff --git a/screencasts/jump-to-definition.mp4
b/screencasts/jump-to-definition.mp4
new file mode 100644
index 0000000..dcf7624
Binary files /dev/null and b/screencasts/jump-to-definition.mp4 differ
diff --git a/test/data/hsinspect-0.0.7-imports.sexp.gz
b/test/data/hsinspect-0.0.7-imports.sexp.gz
deleted file mode 100644
index dc6b7ac..0000000
Binary files a/test/data/hsinspect-0.0.7-imports.sexp.gz and /dev/null differ
diff --git a/test/data/hsinspect-0.0.7-index.sexp.gz
b/test/data/hsinspect-0.0.7-index.sexp.gz
deleted file mode 100644
index aad2677..0000000
Binary files a/test/data/hsinspect-0.0.7-index.sexp.gz and /dev/null differ
diff --git a/test/data/hsinspect-0.0.8-index.sexp.gz
b/test/data/hsinspect-0.0.8-index.sexp.gz
deleted file mode 100644
index 6e95415..0000000
Binary files a/test/data/hsinspect-0.0.8-index.sexp.gz and /dev/null differ
diff --git a/test/data/hsinspect-0.0.9-index.sexp.gz
b/test/data/hsinspect-0.0.9-index.sexp.gz
new file mode 100644
index 0000000..f97612a
Binary files /dev/null and b/test/data/hsinspect-0.0.9-index.sexp.gz differ
diff --git a/test/haskell-tng-hsinspect-test.el
b/test/haskell-tng-hsinspect-test.el
index 803f77e..a4653f6 100644
--- a/test/haskell-tng-hsinspect-test.el
+++ b/test/haskell-tng-hsinspect-test.el
@@ -37,29 +37,27 @@
(ert-deftest haskell-tng-hsinspect-test-import-candidates-latest ()
(let ((index
(haskell-tng--util-read
- (testdata "data/hsinspect-0.0.8-index.sexp.gz"))))
+ (testdata "data/hsinspect-0.0.9-index.sexp.gz"))))
;; function search
(should
(equal
(haskell-tng--hsinspect-import-candidates index "throw")
- '(((unitid . "base")
+ '(((srcid . "base-4.12.0.0")
(module . "Control.Exception.Base")
(name . "throw")
(type . "forall a e. Exception e => e -> a")
(class . id)
- (export (unitid . "base")
- (module . "GHC.Exception"))
+ (export (module . "GHC.Exception"))
(flavour))
- ((unitid . "base")
+ ((srcid . "base-4.12.0.0")
(module . "Control.Exception")
(name . "throw")
(type . "forall a e. Exception e => e -> a")
(class . id)
- (export (unitid . "base")
- (module . "GHC.Exception"))
+ (export (module . "GHC.Exception"))
(flavour))
- ((unitid . "base")
+ ((srcid . "base-4.12.0.0")
(module . "GHC.Exception")
(name . "throw")
(type . "forall a e. Exception e => e -> a")
@@ -71,7 +69,7 @@
(should
(equal
(haskell-tng--hsinspect-import-candidates index ">$<")
- '(((unitid . "base")
+ '(((srcid . "base-4.12.0.0")
(module . "Data.Functor.Contravariant")
(name . ">$<")
(type . "forall (f :: * -> *) a b. Contravariant f => (a -> b) -> f b
-> f a")
@@ -83,7 +81,7 @@
(should
(equal
(haskell-tng--hsinspect-import-candidates index "Contravariant")
- '(((unitid . "base")
+ '(((srcid . "base-4.12.0.0")
(module . "Data.Functor.Contravariant")
(name)
(type . "Contravariant")
@@ -93,12 +91,14 @@
;; TODO constructor search
;;(message "%S" (haskell-tng--hsinspect-import-candidates index
"Contravariant"))
+
+ ;; TODO pattern synonym search
))
(ert-deftest haskell-tng-hsinspect-test-extract-imports ()
(let ((index
(haskell-tng--util-read
- (testdata "data/hsinspect-0.0.8-index.sexp.gz"))))
+ (testdata "data/hsinspect-0.0.9-index.sexp.gz"))))
;; explicit import
(should
@@ -130,6 +130,4 @@
(full . "Data.List.and")))))
))
-;; TODO tests for 0.0.7 data
-
;;; haskell-tng-hsinspect-test.el ends here
- [nongnu] elpa/haskell-tng-mode 0113f37 273/385: hsinspect should work on symbols with FQNs, (continued)
- [nongnu] elpa/haskell-tng-mode 0113f37 273/385: hsinspect should work on symbols with FQNs, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 06e4e76 245/385: support running scripts on nix, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 3179294 267/385: rename ormolu command, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 388df13 282/385: refactor calls to hsinspect to allow for more callers, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 067e8a7 280/385: bugfix fontification of erroneous matches inside strings, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 224e955 307/385: add screencasts to the README, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode b59ce04 356/385: improve testing, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode ee1fb28 345/385: small cleanup, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 1310276 346/385: planning, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 09018bc 352/385: support hsinspect beta releases, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode e73bc19 335/385: jump-to-definition,
ELPA Syncer <=
- [nongnu] elpa/haskell-tng-mode 2f2c500 354/385: defining a test spec, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 232baf8 362/385: less import suggestions, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode ab53433 375/385: reprioritise, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 2b9c6bf 369/385: support dev hsinspect, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 68d1d7c 371/385: bump hsinspect, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 6a31b59 101/385: document the plan for indentation, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 9ec4841 104/385: meh indentation, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode ea46850 207/385: bugfix for compile vs runtime loading, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 63b1c8d 250/385: flip the default newline / comment behaviour, ELPA Syncer, 2021/10/06
- [nongnu] elpa/haskell-tng-mode 83458d6 253/385: reverting newline comment behaviour, ELPA Syncer, 2021/10/06