Subject: bug#64784: closed (LSP vs Emacs indentation [Was: bug#64784: 30.0.50; Eglot: Lisp error: (wrong-type-argument number-or-marker-p return) in eglot--post-self-insert-hook])

From: [email-masked] (GNU bug Tracking System)
Date: Sun, 23 Jul 2023 10:19:01 +0000

Your bug report

#64784: 30.0.50; Eglot: Lisp error: (wrong-type-argument number-or-marker-p return) in eglot--post-self-insert-hook

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
64784: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=64784
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: LSP vs Emacs indentation [Was: bug#64784: 30.0.50; Eglot: Lisp error: (wrong-type-argument number-or-marker-p return) in eglot--post-self-insert-hook]
From: João Távora <[email-masked]>
To: Tassilo Horn <[email-masked]>
Cc: Eli Zaretskii <[email-masked]>, Stefan Monnier <[email-masked]>, [email-masked]
Date: Sun, 23 Jul 2023 11:20:50 +0100
List-Id: <debbugs-submit.debbugs.gnu.org>

Tassilo Horn <[email-masked]> writes:

>> Tassilo, can you test?
> I can say at least that the error is gone.  Thank you!

In that case, I'm closing, but we should keep chatting because this
matter interests me as well.  In fact, let's move this to Emacs-devel.

> So is there a way to stop the indentation wars [between LSP and Emacs's]?

There's quite a bit of overlap in indentation functionality, yes.  For
example, in non-ts c++-mode, there are a lot of indentation knobs, and
they can probably do all this.  But configuring them is difficult (for
me), and I'm not sure I haven't seen at a bug or two.  In practice, I've
accepted I'll never get them to match my team's .clang-format
completely.

A way to "stop" the war is to get one of the sides to surrender.  To
make the LSP side lose, just add symbols to
'eglot-ignored-server-capabilities', like
':documentOnTypeFormattingProvider'.

> Maybe if I could make it so that return and tab would also be
> considered as trigger characters for eglot-format?

If your aim is to make the LSP side "win", I don't think you should use
the "trigger character" technique specifically.  But in Emacs you can of
course bind keys to commands that invoke 'eglot-format' synchronously.

Even better, I think the most correct way is to buffer-locally set
'indent-line-function' and 'indent-region-function', so you can keep the
familiar feeling of TAB.

I've tested this:

* Setting 'indent-region-function' simply to 'eglot-format' apparently
  works.  (Not in cc-mode, which has a tendency for wheel-reinvention.
  Or who knows original invention...  but in any case it's probably time
  for me to move on to c++-ts-mode)

* As to 'indent-line-function', there's no Eglot command that's exactly
  compatible with the protocol, but it's pretty easy to make one:

    (defun eglot-indent-line ()
      (eglot-format (line-beginning-position) (line-end-position)))

After setting 'indent-line-function' and 'indent-region-function' like
this, things seem to work well at first.  But simple things like RET
('newline') fail.  I haven't investigated.  Maybe it's becasue of
electric-indent-mode?  Or maybe just because of the fact that
`eglot-format` asks the language server to do more than just indenting,
namely it also inserts newlines.  Or maybe I'm just not passing in the
correct range.

And then there are the annoying messages in the echo area about edits
successfully applied, but that's easily solved.

I hope you (and/or others) can give this approach (or variations
thereof) some testing.  Maybe with other LSP servers and/or style files.

The reason I find this interesting it that it would IMO not only solve
the indentation wars, it solves fundamental problems of limited-context
indenters such as c++-ts-mode.  Consider the C/C++ textual preprocessor
macros: probably no tree-sitter mode can know exactly what they mean and
how to indent the surrounding code, but a sufficiently smart
project-knowing LSP can.

Are there drawbacks to this "LSP-wins" approach to indentation?
Probably.  Chief among them LSP is very slow when compared things in the
same address space, like an Elisp function or a dynamically linked C
library.

My early impression is that this fact almost certainly matters for
LSP-driven fontification, but not for indentation.

In summary, if we can get a successful approach that feels right for
Emacs users (mostly regarding TAB, newline and region-indenting), maybe
we can enshrine LSP-powered indentation in Eglot-managed buffers just
like we do for Xref, Flymake, Imenu, etc...

João

Subject: 30.0.50; Eglot: Lisp error: (wrong-type-argument number-or-marker-p return) in eglot--post-self-insert-hook
From: Tassilo Horn <[email-masked]>
To: [email-masked]
Date: Sat, 22 Jul 2023 07:06:58 +0200
List-Id: <debbugs-submit.debbugs.gnu.org>

I'm in a rust-ts-mode buffer and ran eglot.  When I hit RET (bound to
newline), I get the following error:

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p return)
  eglot--post-self-insert-hook()
  self-insert-command(1)
  newline(nil 1)
  funcall-interactively(newline nil 1)
  call-interactively(newline nil nil)
  command-execute(newline)

In GNU Emacs 30.0.50 (build 56, x86_64-pc-linux-gnu, GTK+ Version
 3.24.38, cairo version 1.17.8) of 2023-07-21 built on thinkpad-t440p
Repository revision: c55e67081e9873a32b6e665e44f3e5a9c301255f
Repository branch: master
System Description: Arch Linux

Configured using:
 'configure --with-tree-sitter --with-pgtk --with-modules'

Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG
JSON LCMS2 LIBOTF LIBSYSTEMD LIBXML2 MODULES NOTIFY INOTIFY PDUMPER PGTK
PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS
TREE_SITTER WEBP XIM GTK3 ZLIB

Important settings:
  value of $LC_MONETARY: de_DE.utf8
  value of $LC_NUMERIC: de_DE.utf8
  value of $LC_TIME: de_DE.utf8
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Rust

Minor modes in effect:
  eglot-inlay-hints-mode: t
  eglot--managed-mode: t
  flymake-mode: t
  display-fill-column-indicator-mode: t
  display-line-numbers-mode: t
  hexl-follow-ascii: t
  cargo-minor-mode: t
  editorconfig-mode: t
  debbugs-browse-mode: t
  hl-todo-mode: t
  global-aggressive-indent-mode: t
  aggressive-indent-mode: t
  pdf-occur-global-minor-mode: t
  diredfl-global-mode: t
  mu4e-modeline-mode: t
  which-key-mode: t
  highlight-parentheses-mode: t
  yas-global-mode: t
  yas-minor-mode: t
  global-git-commit-mode: t
  magit-auto-revert-mode: t
  server-mode: t
  bug-reference-prog-mode: t
  corfu-history-mode: t
  global-corfu-mode: t
  corfu-mode: t
  vertico-mode: t
  marginalia-mode: t
  minibuffer-depth-indicate-mode: t
  switchy-window-minor-mode: t
  electric-pair-mode: t
  recentf-mode: t
  pixel-scroll-precision-mode: t
  pixel-scroll-mode: t
  override-global-mode: t
  repeat-mode: t
  save-place-mode: t
  savehist-mode: t
  puni-global-mode: t
  puni-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  column-number-mode: t
  line-number-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t

Load-path shadows:
~/Repos/el/mu/mu4e/mu4e hides ~/Repos/el/mu/build/mu4e/mu4e
~/Repos/el/mu/mu4e/mu4e-modeline hides ~/Repos/el/mu/build/mu4e/mu4e-modeline
~/Repos/el/mu/mu4e/mu4e-context hides ~/Repos/el/mu/build/mu4e/mu4e-context
~/Repos/el/mu/mu4e/mu4e-main hides ~/Repos/el/mu/build/mu4e/mu4e-main
~/Repos/el/mu/mu4e/mu4e-vars hides ~/Repos/el/mu/build/mu4e/mu4e-vars
~/Repos/el/mu/mu4e/mu4e-window hides ~/Repos/el/mu/build/mu4e/mu4e-window
~/Repos/el/mu/mu4e/mu4e-speedbar hides ~/Repos/el/mu/build/mu4e/mu4e-speedbar
~/Repos/el/mu/mu4e/mu4e-view hides ~/Repos/el/mu/build/mu4e/mu4e-view
~/Repos/el/mu/mu4e/mu4e-thread hides ~/Repos/el/mu/build/mu4e/mu4e-thread
~/Repos/el/mu/mu4e/mu4e-bookmarks hides ~/Repos/el/mu/build/mu4e/mu4e-bookmarks
~/Repos/el/mu/mu4e/mu4e-org hides ~/Repos/el/mu/build/mu4e/mu4e-org
~/Repos/el/mu/mu4e/mu4e-lists hides ~/Repos/el/mu/build/mu4e/mu4e-lists
~/Repos/el/mu/mu4e/mu4e-actions hides ~/Repos/el/mu/build/mu4e/mu4e-actions
~/Repos/el/mu/mu4e/mu4e-helpers hides ~/Repos/el/mu/build/mu4e/mu4e-helpers
~/Repos/el/mu/mu4e/mu4e-search hides ~/Repos/el/mu/build/mu4e/mu4e-search
~/Repos/el/mu/mu4e/mu4e-server hides ~/Repos/el/mu/build/mu4e/mu4e-server
~/Repos/el/mu/mu4e/mu4e-obsolete hides ~/Repos/el/mu/build/mu4e/mu4e-obsolete
~/Repos/el/mu/mu4e/mu4e-update hides ~/Repos/el/mu/build/mu4e/mu4e-update
~/Repos/el/mu/mu4e/mu4e-draft hides ~/Repos/el/mu/build/mu4e/mu4e-draft
~/Repos/el/mu/mu4e/mu4e-message hides ~/Repos/el/mu/build/mu4e/mu4e-message
~/Repos/el/mu/mu4e/mu4e-compose hides ~/Repos/el/mu/build/mu4e/mu4e-compose
~/Repos/el/mu/mu4e/mu4e-headers hides ~/Repos/el/mu/build/mu4e/mu4e-headers
~/Repos/el/mu/mu4e/mu4e-query-items hides ~/Repos/el/mu/build/mu4e/mu4e-query-items
~/Repos/el/mu/mu4e/mu4e-notification hides ~/Repos/el/mu/build/mu4e/mu4e-notification
~/Repos/el/mu/mu4e/mu4e-contacts hides ~/Repos/el/mu/build/mu4e/mu4e-contacts
~/Repos/el/mu/mu4e/mu4e-icalendar hides ~/Repos/el/mu/build/mu4e/mu4e-icalendar
~/Repos/el/mu/mu4e/mu4e-mark hides ~/Repos/el/mu/build/mu4e/mu4e-mark
~/Repos/el/mu/mu4e/mu4e-contrib hides ~/Repos/el/mu/build/mu4e/mu4e-contrib
~/Repos/el/mu/mu4e/mu4e-folders hides ~/Repos/el/mu/build/mu4e/mu4e-folders
~/Repos/el/mu/mu4e/mu4e-mime-parts hides ~/Repos/el/mu/build/mu4e/mu4e-mime-parts
/home/horn/.emacs.d/elpa/transient-20230602.2121/transient hides /home/horn/Repos/el/emacs/lisp/transient

Features:
(shadow face-remap so-long view emacsbug shortdoc comp comp-cstr
help-fns radix-tree cl-print cus-start cape-keyword cape eglot
external-completion jsonrpc flymake-proc flymake ert debug backtrace
tramp-cmds rust-ts-mode c-ts-common puni pulse
display-fill-column-indicator display-line-numbers ef-cherie-theme
generic totp-widget totp hexl bindat yaml-mode fish-mode cargo xref
project cargo-process web-mode disp-table preview-latex tex-site
editorconfig editorconfig-core editorconfig-core-handle
editorconfig-fnmatch elfeed-show elfeed-search vc-hg vc-bzr vc-src
vc-sccs vc-svn vc-cvs vc-rcs log-view debbugs-browse elfeed-csv elfeed
elfeed-curl elfeed-log elfeed-db elfeed-lib avl-tree generator url-queue
xml-query hl-todo aggressive-indent rainbow-mode pdf-occur tablist
tablist-filter semantic/wisent/comp semantic/wisent
semantic/wisent/wisent semantic/util-modes semantic/util semantic
semantic/tag semantic/lex semantic/fw mode-local cedet pdf-isearch
pdf-misc pdf-tools pdf-view jka-compr pdf-cache pdf-info tq pdf-util
pdf-macs image-mode exif vc-git vc-dir ewoc epa-file diredfl dired-x
mu4e-icalendar gnus-icalendar icalendar diary-lib diary-loaddefs mu4e
mu4e-org mu4e-notification notifications mu4e-main mu4e-view
mu4e-mime-parts mu4e-headers mu4e-thread mu4e-compose mu4e-draft
mu4e-actions org-capture org-refile org ob ob-tangle ob-ref ob-lob
ob-table ob-exp org-macro org-src ob-comint org-pcomplete org-list
org-footnote org-faces org-entities ob-emacs-lisp ob-core ob-eval
org-cycle org-table ol org-fold org-fold-core org-keys oc org-loaddefs
find-func cal-menu calendar cal-loaddefs org-compat org-version org-macs
smtpmail mu4e-search mu4e-lists mu4e-bookmarks mu4e-mark mu4e-message
flow-fill mule-util hl-line mu4e-contacts mu4e-update mu4e-folders
mu4e-context mu4e-query-items mu4e-server mu4e-modeline mu4e-vars
mu4e-helpers mu4e-config mu4e-window magit-bookmark bookmark ido
mu4e-obsolete hippie-exp auto-dictionary flyspell ispell tramp-smb
which-key highlight-parentheses restclient yasnippet forge-list
forge-commands forge-semi forge-bitbucket buck forge-gogs gogs
forge-gitea gtea forge-gitlab glab forge-github ghub-graphql treepy
gsexp ghub let-alist forge-notify forge-revnote forge-pullreq
forge-issue forge-topic yaml forge-post markdown-mode thingatpt noutline
outline forge-repo forge forge-core forge-db closql
emacsql-sqlite-common emacsql emacsql-compiler eieio-base
magit-submodule magit-blame magit-stash magit-reflog magit-bisect
magit-push magit-pull magit-fetch magit-clone magit-remote magit-commit
magit-sequence magit-notes magit-worktree magit-tag magit-merge
magit-branch magit-reset magit-files magit-refs magit-status magit
magit-repos magit-apply magit-wip magit-log which-func imenu magit-diff
smerge-mode diff diff-mode git-commit log-edit pcvs-util add-log
magit-core magit-autorevert autorevert filenotify magit-margin
magit-transient magit-process with-editor server magit-mode magit-git
magit-base magit-section cursor-sensor crm dash visual-filename-abbrev
rg vc vc-dispatcher rg-info-hack advice rg-menu transient rg-ibuffer
rg-result wgrep-rg wgrep rg-history rg-header ibuf-ext ibuffer
ibuffer-loaddefs grep compile debbugs soap-client url-http url-auth
url-gw nsm warnings rng-xsd rng-dt rng-util xsd-regexp bug-reference
kind-icon svg-lib color corfu-history corfu vertico marginalia icomplete
mb-depth use-package-diminish switchy-window compat elec-pair recentf
tree-widget pixel-scroll cua-base edmacro kmacro use-package-bind-key
bind-key diminish repeat treesit saveplace tramp-sh savehist smiley
gnus-art mm-uu mml2015 mm-view mml-smime smime gnutls dig gnus-sum shr
pixel-fill kinsoku url-file svg dom gnus-group gnus-undo gnus-start
gnus-dbus dbus xml gnus-cloud nnimap nnmail mail-source utf7 nnoo
gnus-spec gnus-int gnus-range message sendmail yank-media puny dired
dired-loaddefs rfc822 mml mml-sec epa derived epg rfc6068 epg-config
mm-decode mm-bodies mm-encode mail-parse rfc2231 rfc2047 rfc2045
ietf-drums mailabbrev gmm-utils mailheader gnus-win gnus nnheader
gnus-util text-property-search mm-util mail-prsvr mail-utils range
ef-themes cl-extra help-mode use-package-ensure use-package-core
finder-inf tramp rx tramp-loaddefs trampver tramp-integration files-x
tramp-compat xdg shell pcomplete comint ansi-osc ring parse-time iso8601
time-date format-spec ansi-color cus-edit pp cus-load icons wid-edit
aggressive-indent-autoloads auto-dictionary-autoloads cape-autoloads
cargo-autoloads clojure-mode-autoloads corfu-autoloads
csv-mode-autoloads debbugs-autoloads diminish-autoloads
diredfl-autoloads editorconfig-autoloads ef-themes-autoloads
elfeed-autoloads ement-autoloads expand-region-autoloads
fish-mode-autoloads forge-autoloads closql-autoloads emacsql-autoloads
ghub-autoloads highlight-parentheses-autoloads hl-todo-autoloads
kind-icon-autoloads magit-autoloads pcase git-commit-autoloads
marginalia-autoloads markdown-mode-autoloads mastodon-autoloads
pdf-tools-autoloads persist-autoloads plz-autoloads puni-autoloads
easy-mmode rainbow-mode-autoloads rcirc-color-autoloads
request-autoloads restclient-autoloads rg-autoloads svg-lib-autoloads
symbol-overlay-autoloads tablist-autoloads taxy-magit-section-autoloads
taxy-autoloads magit-section-autoloads totp-autoloads
transient-autoloads treepy-autoloads ts-autoloads s-autoloads
dash-autoloads vertico-autoloads visual-filename-abbrev-autoloads
web-mode-autoloads wgrep-autoloads which-key-autoloads
with-editor-autoloads info compat-autoloads yaml-autoloads
yaml-mode-autoloads yasnippet-autoloads package browse-url url url-proxy
url-privacy url-expand url-methods url-history url-cookie
generate-lisp-file url-domsuf url-util mailcap url-handlers url-parse
auth-source cl-seq eieio eieio-core cl-macs password-cache json subr-x
map byte-opt gv bytecomp byte-compile url-vars cl-loaddefs cl-lib rmc
iso-transl tooltip cconv eldoc paren electric uniquify ediff-hook
vc-hooks lisp-float-type elisp-mode mwheel term/pgtk-win pgtk-win
term/common-win pgtk-dnd tool-bar dnd fontset image regexp-opt fringe
tabulated-list replace newcomment text-mode lisp-mode prog-mode register
page tab-bar menu-bar rfn-eshadow isearch easymenu timer select
scroll-bar mouse jit-lock font-lock syntax font-core term/tty-colors
frame minibuffer nadvice seq simple cl-generic indonesian philippine
cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao
korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech
european ethiopic indian cyrillic chinese composite emoji-zwj charscript
charprop case-table epa-hook jka-cmpr-hook help abbrev obarray oclosure
cl-preloaded button loaddefs theme-loaddefs faces cus-face macroexp
files window text-properties overlay sha1 md5 base64 format env
code-pages mule custom widget keymap hashtable-print-readable backquote
threads dbusbind inotify dynamic-setting system-font-setting
font-render-setting cairo gtk pgtk lcms2 multi-tty move-toolbar
make-network-process emacs)

Memory information:
((conses 16 817006 172619) (symbols 48 55210 5) (strings 32 241804 7684)
 (string-bytes 1 6997482) (vectors 16 99513) (vector-slots 8 1205973 165006)
 (floats 8 713 2345) (intervals 56 2394 475) (buffers 984 22))