Skip to content

Commit

Permalink
Support the "new" eldoc API; closes #716
Browse files Browse the repository at this point in the history
  • Loading branch information
greghendershott committed Sep 12, 2024
1 parent bee055d commit d163eae
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 172 deletions.
12 changes: 6 additions & 6 deletions doc/racket-mode.org
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
:COPYING: t
:END:

Copyright (C) 2013-2022 by Greg Hendershott.
Copyright (C) 2013-2024 by Greg Hendershott.

SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -366,15 +366,15 @@ You can customize where the REPL buffer is displayed by adding an item to the Em

** eldoc

By default Racket Mode sets ~eldoc-documentation-function~ to ~nil~ --- no ~eldoc-mode~ support. You may set it to ~racket-eldoc-function~ in a ~racket-mode-hook~ and ~racket-repl-mode-hook~ if you really want to use ~eldoc-mode~ with Racket. But it is not a very satisfying experience because Racket is not a very "eldoc-friendly" language. Although Racket Mode attempts to discover argument lists, contracts, or types this doesn't work in many common cases:
Various modes add local hooks to ~eldoc-documentation-functions~.

- Many Racket primitives are defined in ~#%kernel~ or ~#%runtime~. There's no easy way to determine their argument lists. Most do not ~provide~ a contract.
- ~racket-eldoc-app~ looks for function/macro application and returns information such as a signature or type. Added by ~racket-mode~, ~racket-repl-mode~, and ~racket-hash-lang-mode~ when the hash-lang appears to use s-expressions. Keep in mind that, because Racket is not a "doc string" system, these summaries are impoverished compared to the documentation you get from {{{ref(racket-xp-describe)}}} or {{{ref(racket-xp-documentation)}}}.

- Many of the interesting Racket forms are syntax (macros) not functions. There's no easy way to determine their "argument lists".
- ~racket-xp-eldoc-help-echo~ returns help-echo information added by ~racket-xp-mode~ from check-syntax annotations. This is lower precedence; ~eldoc-documentation-strategy~ determines whether or not it will be displayed.

- When a form has documentation, Racket Mode can show the \"bluebox\" -- but often that does not fit in a single line as you would normally expect with eldoc.
Some people use the third-party package ~eldoc-box~ to show information in a child frame (like a "tooltip") instead of the echo area.

A more satisfying experience is to use {{{ref(racket-xp-describe)}}} or {{{ref(racket-xp-documentation)}}}.
Note: Racket Mode does not support the "old" eldoc API that uses ~eldoc-documentation-function~, singular.

** Start faster

Expand Down
111 changes: 57 additions & 54 deletions doc/racket-mode.texi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@c %**end of header

@copying
Copyright (C) 2013-2022 by Greg Hendershott.
Copyright (C) 2013-2024 by Greg Hendershott.

SPDX-License-Identifier: GPL-3.0-or-later
@end copying
Expand Down Expand Up @@ -822,20 +822,19 @@ You can customize where the REPL buffer is displayed by adding an item to the Em
@node eldoc
@section eldoc

By default Racket Mode sets @code{eldoc-documentation-function} to @code{nil} --- no @code{eldoc-mode} support. You may set it to @code{racket-eldoc-function} in a @code{racket-mode-hook} and @code{racket-repl-mode-hook} if you really want to use @code{eldoc-mode} with Racket. But it is not a very satisfying experience because Racket is not a very ``eldoc-friendly'' language. Although Racket Mode attempts to discover argument lists, contracts, or types this doesn't work in many common cases:
Various modes add local hooks to @code{eldoc-documentation-functions}.

@itemize
@item
Many Racket primitives are defined in @code{#%kernel} or @code{#%runtime}. There's no easy way to determine their argument lists. Most do not @code{provide} a contract.
@code{racket-eldoc-app} looks for function/macro application and returns information such as a signature or type. Added by @code{racket-mode}, @code{racket-repl-mode}, and @code{racket-hash-lang-mode} when the hash-lang appears to use s-expressions. Keep in mind that, because Racket is not a ``doc string'' system, these summaries are impoverished compared to the documentation you get from @ref{racket-xp-describe} or @ref{racket-xp-documentation}.

@item
Many of the interesting Racket forms are syntax (macros) not functions. There's no easy way to determine their ``argument lists''.

@item
When a form has documentation, Racket Mode can show the \``bluebox\'' -- but often that does not fit in a single line as you would normally expect with eldoc.
@code{racket-xp-eldoc-help-echo} returns help-echo information added by @code{racket-xp-mode} from check-syntax annotations. This is lower precedence; @code{eldoc-documentation-strategy} determines whether or not it will be displayed.
@end itemize

A more satisfying experience is to use @ref{racket-xp-describe} or @ref{racket-xp-documentation}.
Some people use the third-party package @code{eldoc-box} to show information in a child frame (like a ``tooltip'') instead of the echo area.

Note: Racket Mode does not support the ``old'' eldoc API that uses @code{eldoc-documentation-function}, singular.

@node Start faster
@section Start faster
Expand Down Expand Up @@ -1298,21 +1297,6 @@ even ``let'' forms:

Minor mode to let you always type @code{[}' to insert @code{(} or @code{[} automatically.

This is a minor mode. If called interactively, toggle the
@code{Racket-Smart-Open-Bracket mode} mode. If the prefix argument is
positive, enable the mode, and if it is zero or negative, disable
the mode.

If called from Lisp, toggle the mode if ARG is @code{toggle}. Enable
the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.

To check whether the minor mode is enabled in the current buffer,
evaluate @ref{racket-smart-open-bracket-mode}.

The mode's hook is called both when the mode is enabled and when
it is disabled.

Behaves like the ``Automatically adjust opening square brackets''
feature in Dr. Racket.

Expand Down Expand Up @@ -1355,6 +1339,21 @@ Tip: When using this with @ref{racket-hash-lang-mode}, you may want
to use @ref{racket-hash-lang-module-language-hook} to enable it IFF
the module langugage is something like ``racket''.

This is a minor mode. If called interactively, toggle the
@code{Racket-Smart-Open-Bracket mode} mode. If the prefix argument is
positive, enable the mode, and if it is zero or negative, disable
the mode.

If called from Lisp, toggle the mode if ARG is @code{toggle}. Enable
the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.

To check whether the minor mode is enabled in the current buffer,
evaluate @ref{racket-smart-open-bracket-mode}.

The mode's hook is called both when the mode is enabled and when
it is disabled.

@node racket-insert-closing
@subsection racket-insert-closing

Expand Down Expand Up @@ -1724,20 +1723,6 @@ anything else, do @code{prog-indent-sexp}.

A minor mode that analyzes expanded code to explain and explore.

This is a minor mode. If called interactively, toggle the
@code{Racket-Xp mode} mode. If the prefix argument is positive,
enable the mode, and if it is zero or negative, disable the mode.

If called from Lisp, toggle the mode if ARG is @code{toggle}. Enable
the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.

To check whether the minor mode is enabled in the current buffer,
evaluate @ref{racket-xp-mode}.

The mode's hook is called both when the mode is enabled and when
it is disabled.

This minor mode is an optional enhancement to @ref{racket-mode} edit
buffers. Like any minor mode, you can turn it on or off for a
specific buffer. If you always want to use it, put the following
Expand Down Expand Up @@ -1879,7 +1864,7 @@ will stop after the first syntax error, some like Typed Racket
will try to collect and report multiple errors.

You may use @code{xref-find-definitions} @kbd{M-.} ,
@code{xref-pop-marker-stack} @kbd{M-,} , and
@code{xref-pop-marker-stack} @kbd{M-x} @code{xref-pop-marker-stack}, and
@code{xref-find-references}: @ref{racket-xp-mode} adds a backend to the
variable @code{xref-backend-functions}. This backend uses information
from the drracket/check-syntax static analysis. Its ability to
Expand Down Expand Up @@ -1936,6 +1921,22 @@ commands directly to whatever keys you prefer.
@tab @ref{racket-xp-next-definition}
@end multitable



This is a minor mode. If called interactively, toggle the
@code{Racket-Xp mode} mode. If the prefix argument is positive,
enable the mode, and if it is zero or negative, disable the mode.

If called from Lisp, toggle the mode if ARG is @code{toggle}. Enable
the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.

To check whether the minor mode is enabled in the current buffer,
evaluate @ref{racket-xp-mode}.

The mode's hook is called both when the mode is enabled and when
it is disabled.

@node racket-xp-describe
@subsection racket-xp-describe

Expand Down Expand Up @@ -2181,7 +2182,7 @@ Racket Describe buffer for that module's version of the thing.
Major mode for Racket REPL@.

You may use @code{xref-find-definitions} @kbd{M-.} and
@code{xref-pop-marker-stack} @kbd{M-,} :
@code{xref-pop-marker-stack} @kbd{M-x} @code{xref-pop-marker-stack}:
@ref{racket-repl-mode} adds a backend to the variable
@code{xref-backend-functions}. This backend uses information about
identifier bindings and modules from the REPL's namespace.
Expand Down Expand Up @@ -2547,20 +2548,6 @@ or penultimate step during initialization.

Minor mode for debug breaks.

This is a minor mode. If called interactively, toggle the
@code{Racket-Debug mode} mode. If the prefix argument is positive,
enable the mode, and if it is zero or negative, disable the mode.

If called from Lisp, toggle the mode if ARG is @code{toggle}. Enable
the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.

To check whether the minor mode is enabled in the current buffer,
evaluate @ref{racket-debug-mode}.

The mode's hook is called both when the mode is enabled and when
it is disabled.

This feature is @strong{@strong{EXPERIMENTAL}}!!! It is likely to have
significant limitations and bugs. You are welcome to open an
issue to provide feedback. Please understand that this feature
Expand Down Expand Up @@ -2628,6 +2615,22 @@ provides shortcut keys:
@tab @code{racket-debug-step}
@end multitable



This is a minor mode. If called interactively, toggle the
@code{Racket-Debug mode} mode. If the prefix argument is positive,
enable the mode, and if it is zero or negative, disable the mode.

If called from Lisp, toggle the mode if ARG is @code{toggle}. Enable
the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.

To check whether the minor mode is enabled in the current buffer,
evaluate @ref{racket-debug-mode}.

The mode's hook is called both when the mode is enabled and when
it is disabled.

@node racket-repl-clear
@subsection racket-repl-clear

Expand Down Expand Up @@ -4361,4 +4364,4 @@ Face @ref{racket-repl-mode} uses for output to current-error-port.

Face @ref{racket-hash-lang-mode} uses for text tokens.

@bye
@bye
98 changes: 71 additions & 27 deletions racket-eldoc.el
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
;;; racket-eldoc.el -*- lexical-binding: t -*-

;; Copyright (c) 2013-2020 by Greg Hendershott.
;; Copyright (c) 2013-2024 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.

;; Author: Greg Hendershott
Expand All @@ -9,32 +9,76 @@
;; SPDX-License-Identifier: GPL-3.0-or-later

(require 'racket-cmd)
(require 'racket-back-end)

(defun racket--do-eldoc (how repl-session-id)
(and (racket--cmd-open-p)
(> (point) (point-min))
(save-excursion
(condition-case _
;; The char-before and looking-at-p checks below are to
;; skip when the sexp is quoted or when its first elem
;; couldn't be a Racket function name.
(let* ((beg (progn
(backward-up-list)
(and (not (memq (char-before) '(?` ?' ?,)))
(progn (forward-char 1) (point)))))
(beg (and beg (looking-at-p "[^0-9#'`,\"]") beg))
(end (and beg (progn (forward-sexp) (point))))
(end (and end
(char-after (point))
(eq ?\s (char-syntax (char-after (point))))
end))
(sym (and beg end (buffer-substring-no-properties beg end)))
(how (racket-how-front-to-back how))
(str (and sym (racket--cmd/await repl-session-id
`(type ,how ,sym)))))
str)
(scan-error nil)))))

;; Given that `eldoc-documentation-functions' is plural, you might
;; wonder why `racket--eldoc-sexp-app' employs two methods. Wouldn't
;; be better to split it into two functions? (And also have
;; racket-xp-mode add/remove one of the functions, which looks for the
;; racket-xp-doc text property?)
;;
;; Although that sounds reasonable, and I tried that way first, it's
;; not satisfactory.
;;
;; The two methods aren't necessarily mutually exclusive. Both could
;; succeed, often with exactly the same result.
;;
;; Therefore for some values of `eldoc-documentation-strategy' both
;; might be shown -- which would waste screen space as well as time.
;;
;; That's why instead this is a single function that is in charge of
;; trying each method and returning a single result (or not).

(defun racket--eldoc-sexp-app (how repl-session-id callback)
"Call eldoc CALLBACK about the function application around point.
Assume sexp language. First look for doc text property added by
`racket-xp-mode'. Otherwise try using the back end \"type\"
command, which is when HOW and REPL-SESSION-ID are used."
(when (and (racket--cmd-open-p)
(> (point) (point-min)))
(save-excursion
(ignore-errors
(backward-up-list)
(forward-char 1)
(or (racket--eldoc-bluebox callback)
(racket--eldoc-type how repl-session-id callback))))))

(defun racket--eldoc-bluebox (callback &rest _more)
"Obtain bluebox from tag in racket-xp-doc text property."
(pcase (get-text-property (point) 'racket-xp-doc)
(`(,_path ,_anchor ,tag)
(let* ((end (next-single-property-change (point) 'racket-xp-doc))
(thing (buffer-substring-no-properties (point) end)))
(racket--cmd/async
nil
`(bluebox ,tag)
(lambda (str)
(racket--eldoc-do-callback callback thing str)))
t))))

(defun racket--eldoc-type (how repl-session-id callback)
"Obtain a \"type\" summary string from the back end.
This might be a bluebox, or a function signature discovered from
the surface syntax, or Typed Racket type information."
(let* ((end (progn (forward-sexp) (point)))
(thing (buffer-substring-no-properties (point) end)))
(when thing
(racket--cmd/async
repl-session-id
`(type ,(racket-how-front-to-back how)
,thing)
(lambda (str)
(racket--eldoc-do-callback callback thing str)))
t)))

(defun racket--eldoc-do-callback (callback thing str)
(if str
(funcall callback
(concat (when (string-match-p "\n" str)
"\n")
str)
:thing thing
:face 'font-lock-function-name-face)
(funcall callback "")))

(provide 'racket-eldoc)

Expand Down
15 changes: 13 additions & 2 deletions racket-hash-lang.el
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
;;; racket-hash-lang.el -*- lexical-binding: t; -*-

;; Copyright (c) 2020-2023 by Greg Hendershott.
;; Copyright (c) 2020-2024 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.

;; Author: Greg Hendershott
Expand All @@ -12,6 +12,7 @@
(require 'elec-pair)
(require 'seq)
(require 'racket-cmd)
(require 'racket-eldoc)
(require 'racket-mode)
(require 'racket-repl)

Expand Down Expand Up @@ -260,7 +261,7 @@ A discussion of the information provided by a Racket language:
(setq-local blink-paren-function nil)
(setq-local imenu-create-index-function nil)
(setq-local completion-at-point-functions nil) ;rely on racket-xp-mode
(setq-local eldoc-documentation-function nil)
(setq-local eldoc-documentation-functions nil) ;will change in on-new-lang
(setq racket-submodules-at-point-function nil) ;might change in on-new-lang
;; Create back end hash-lang object.
;;
Expand Down Expand Up @@ -421,6 +422,16 @@ lang's attributes that we care about have changed."
;; like `up-list' use `forward-sexp'.
(setq-local forward-sexp-function (unless (plist-get plist 'racket-grouping)
#'racket-hash-lang-forward-sexp))
;; AFAICT we have no reliable way to determine function
;; application in the general case.
(when (boundp 'eldoc-documentation-functions)
(if (plist-get plist 'racket-grouping)
(add-hook 'eldoc-documentation-functions
#'racket-eldoc-app
nil t)
(remove-hook 'eldoc-documentation-functions
#'racket-eldoc-app
t)))
(syntax-ppss-flush-cache (point-min))
(setq-local indent-line-function
#'racket-hash-lang-indent-line-function)
Expand Down
Loading

0 comments on commit d163eae

Please sign in to comment.