summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Coquand <marc@mccd.space>2024-07-01 21:26:30 -0500
committerMarc Coquand <marc@mccd.space>2024-07-01 21:26:30 -0500
commit0a20357d4585da91d92252972f3eb7b715ff64ab (patch)
treec1e228d72b6331e89f72d99a1ba4ba1807d60381
downloadopenbsd-0a20357d4585da91d92252972f3eb7b715ff64ab.tar.gz
openbsd-0a20357d4585da91d92252972f3eb7b715ff64ab.tar.bz2
openbsd-0a20357d4585da91d92252972f3eb7b715ff64ab.zip
initial commit
-rw-r--r--Xresources5
-rw-r--r--config/qutebrowser/autoconfig.yml139
-rw-r--r--cwmrc63
-rw-r--r--gitconfig47
-rw-r--r--kshrc19
-rwxr-xr-xlocal/bin/pfetch1958
-rw-r--r--mbsyncrc36
-rw-r--r--profile24
-rw-r--r--tigrc103
-rw-r--r--tmux.conf3
-rw-r--r--vim/downloads/opt/snippets/snippets.vim6
-rw-r--r--vim/pack/downloads/opt/lsp/.gitignore8
-rw-r--r--vim/pack/downloads/opt/lsp/LICENSE21
-rw-r--r--vim/pack/downloads/opt/lsp/README.md234
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim194
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim194
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim501
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim135
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim32
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim708
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim920
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim305
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim137
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim235
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim1304
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim1982
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim757
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim168
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/options.vim179
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim304
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim107
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim264
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim145
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim82
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim1014
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim321
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim174
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/util.vim364
-rw-r--r--vim/pack/downloads/opt/lsp/doc/lsp.txt2001
-rw-r--r--vim/pack/downloads/opt/lsp/ftplugin/lspgfm.vim78
-rw-r--r--vim/pack/downloads/opt/lsp/plugin/lsp.vim163
-rw-r--r--vim/pack/downloads/opt/lsp/syntax/lspgfm.vim23
-rw-r--r--vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim478
-rw-r--r--vim/pack/downloads/opt/lsp/test/clangd_tests.vim1782
-rw-r--r--vim/pack/downloads/opt/lsp/test/common.vim166
-rw-r--r--vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump10
-rw-r--r--vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump10
-rw-r--r--vim/pack/downloads/opt/lsp/test/gopls_tests.vim121
-rw-r--r--vim/pack/downloads/opt/lsp/test/markdown_tests.vim305
-rw-r--r--vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim14
-rw-r--r--vim/pack/downloads/opt/lsp/test/run_tests.cmd17
-rwxr-xr-xvim/pack/downloads/opt/lsp/test/run_tests.sh51
-rw-r--r--vim/pack/downloads/opt/lsp/test/runner.vim55
-rw-r--r--vim/pack/downloads/opt/lsp/test/rust_tests.vim137
-rw-r--r--vim/pack/downloads/opt/lsp/test/screendump.vim117
-rw-r--r--vim/pack/downloads/opt/lsp/test/start_tsserver.vim10
-rw-r--r--vim/pack/downloads/opt/lsp/test/term_util.vim125
-rw-r--r--vim/pack/downloads/opt/lsp/test/tsserver_tests.vim43
l---------vim/pack/downloads/opt/snippets/plugin/snippets.vim1
-rw-r--r--vim/swap/%home%mccd%.xsession.swpbin0 -> 12288 bytes
-rw-r--r--vim/swap/%home%mccd.swpbin0 -> 69632 bytes
-rw-r--r--xenodm/Xresources82
-rwxr-xr-xxenodm/Xsetup_019
-rwxr-xr-xxsession14
64 files changed, 19014 insertions, 0 deletions
diff --git a/Xresources b/Xresources
new file mode 100644
index 0000000..2fbfced
--- /dev/null
+++ b/Xresources
@@ -0,0 +1,5 @@
+Xcursor.theme: Adwaita
+Xcursor.size: 34
+XTerm*faceSize: 7
+XTerm*faceName: Envy Code R
+XTerm.termName: xterm-256color
diff --git a/config/qutebrowser/autoconfig.yml b/config/qutebrowser/autoconfig.yml
new file mode 100644
index 0000000..a578568
--- /dev/null
+++ b/config/qutebrowser/autoconfig.yml
@@ -0,0 +1,139 @@
+# If a config.py file exists, this file is ignored unless it's explicitly loaded
+# via config.load_autoconfig(). For more information, see:
+# https://github.com/qutebrowser/qutebrowser/blob/main/doc/help/configuring.asciidoc#loading-autoconfigyml
+# DO NOT edit this file by hand, qutebrowser will overwrite it.
+# Instead, create a config.py - see :help for details.
+
+config_version: 2
+settings:
+ colors.completion.category.bg:
+ global: white
+ colors.completion.category.border.bottom:
+ global: white
+ colors.completion.category.border.top:
+ global: white
+ colors.completion.category.fg:
+ global: black
+ colors.completion.even.bg:
+ global: white
+ colors.completion.fg:
+ global: black
+ colors.completion.item.selected.bg:
+ global: black
+ colors.completion.item.selected.border.bottom:
+ global: black
+ colors.completion.item.selected.border.top:
+ global: black
+ colors.completion.item.selected.fg:
+ global: white
+ colors.completion.item.selected.match.fg:
+ global: white
+ colors.completion.match.fg:
+ global: red
+ colors.completion.odd.bg:
+ global: white
+ colors.completion.scrollbar.bg:
+ global: black
+ colors.completion.scrollbar.fg:
+ global: white
+ colors.downloads.bar.bg:
+ global: white
+ colors.downloads.start.bg:
+ global: white
+ colors.downloads.start.fg:
+ global: black
+ colors.downloads.stop.bg:
+ global: white
+ colors.downloads.stop.fg:
+ global: black
+ colors.hints.bg:
+ global: yellow
+ colors.prompts.bg:
+ global: white
+ colors.prompts.border:
+ global: white
+ colors.prompts.fg:
+ global: black
+ colors.prompts.selected.fg:
+ global: black
+ colors.statusbar.command.bg:
+ global: white
+ colors.statusbar.command.fg:
+ global: black
+ colors.statusbar.insert.bg:
+ global: darkgreen
+ colors.statusbar.normal.bg:
+ global: white
+ colors.statusbar.normal.fg:
+ global: black
+ colors.statusbar.passthrough.bg:
+ global: darkblue
+ colors.statusbar.passthrough.fg:
+ global: black
+ colors.statusbar.progress.bg:
+ global: black
+ colors.statusbar.url.fg:
+ global: black
+ colors.statusbar.url.hover.fg:
+ global: black
+ colors.statusbar.url.success.http.fg:
+ global: black
+ colors.statusbar.url.success.https.fg:
+ global: black
+ colors.tabs.bar.bg:
+ global: white
+ colors.tabs.even.bg:
+ global: white
+ colors.tabs.even.fg:
+ global: black
+ colors.tabs.odd.bg:
+ global: white
+ colors.tabs.odd.fg:
+ global: black
+ colors.tabs.pinned.even.bg:
+ global: white
+ colors.tabs.pinned.even.fg:
+ global: white
+ colors.tabs.pinned.odd.bg:
+ global: white
+ colors.tabs.pinned.odd.fg:
+ global: white
+ colors.tabs.pinned.selected.odd.bg:
+ global: '#777777'
+ colors.tabs.pinned.selected.odd.fg:
+ global: white
+ colors.tabs.selected.even.bg:
+ global: '#559999'
+ colors.tabs.selected.even.fg:
+ global: white
+ colors.tabs.selected.odd.bg:
+ global: gray
+ colors.tabs.selected.odd.fg:
+ global: white
+ fonts.default_family:
+ global: Iosevka Aile
+ fonts.default_size:
+ global: 7pt
+ fonts.downloads:
+ global: default_size default_family
+ fonts.web.family.fantasy:
+ global: Iosevka Aile
+ fonts.web.family.fixed:
+ global: Iosevka Aile
+ fonts.web.family.sans_serif:
+ global: Iosevka Aile
+ fonts.web.family.serif:
+ global: Iosevka Aile
+ fonts.web.family.standard:
+ global: Iosevka Aile
+ fonts.web.size.default:
+ global: 16
+ tabs.favicons.show:
+ global: never
+ tabs.show:
+ global: switching
+ url.searchengines:
+ global:
+ DEFAULT: https://kagi.com/search?q={}
+ zoom.default:
+ global: 150%
diff --git a/cwmrc b/cwmrc
new file mode 100644
index 0000000..5140e1d
--- /dev/null
+++ b/cwmrc
@@ -0,0 +1,63 @@
+sticky yes
+fontname "Iosevka Aile:pixelsize=18:Normal"
+borderwidth 5
+autogroup 0 xclock,XClock
+autogroup 0 xarclock, XClock
+autogroup 0 clock, clock
+autogroup 0 xclock, xterm
+ignore xclock
+
+unbind-key all
+
+color menufg "#FFFFFF" #black
+color font "#000000" #black
+color menubg "#F5E094" #white
+color selfont "#000000" #black
+color activeborder "#559999"
+color inactiveborder "#E8E8E8"
+
+bind-mouse 4-3 window-resize
+bind-mouse 4-1 window-move
+bind-key 4-s menu-window
+bind-key 4-Return xterm
+bind-key 4-e "xterm -e aerc"
+bind-key 4-q window-close
+bind-key 4-m window-fullscreen
+bind-key 4-l window-vtile
+bind-key 4-u window-vmaximize
+bind-key 4-f window-move-right-big
+bind-key 4S-f window-resize-right-big
+bind-key 4-n window-move-down-big
+bind-key 4S-n window-resize-down-big
+bind-key 4-p window-move-up-big
+bind-key 4S-p window-resize-up-big
+bind-key 4-b window-move-left-big
+bind-key 4S-b window-resize-left-big
+bind-key 4S-r restart
+bind-key 4-space menu-exec
+bind-key 4-Tab window-cycle
+bind-key 4-h window-label
+
+bind-key 4-1 group-only-1
+bind-key 4-2 group-only-2
+bind-key 4-3 group-only-3
+bind-key 4-4 group-only-4
+bind-key 4-5 group-only-5
+bind-key 4-6 group-only-6
+bind-key 4-7 group-only-7
+bind-key 4-8 group-only-8
+bind-key 4-9 group-only-9
+bind-key 4-0 group-only-0
+
+bind-key 4S-1 window-movetogroup-1
+bind-key 4S-2 window-movetogroup-2
+bind-key 4S-3 window-movetogroup-3
+bind-key 4S-4 window-movetogroup-4
+bind-key 4S-5 window-movetogroup-5
+bind-key 4S-6 window-movetogroup-6
+bind-key 4S-7 window-movetogroup-7
+bind-key 4S-8 window-movetogroup-8
+bind-key 4S-9 window-movetogroup-9
+bind-key 4S-0 window-movetogroup-0
+bind-key 4-comma group-cycle
+
diff --git a/gitconfig b/gitconfig
new file mode 100644
index 0000000..13f29c4
--- /dev/null
+++ b/gitconfig
@@ -0,0 +1,47 @@
+[alias]
+ pr = "!gh pr"
+ issue = "!gh issue"
+ bug = "!git-bug"
+[user]
+ email = marc@mccd.space
+ name = Marc Coquand
+[credential "https://github.com"]
+ helper = !gh auth git-credential
+[credential "https://gist.github.com"]
+ helper = !gh auth git-credential
+[credential "smtp://marcc%40fastmail.fr@smtp.fastmail.com%3a465"]
+ helper =
+ helper = !pass 'fastmail/git'
+[color]
+ ui = false
+[mergetool]
+ layout = LOCAL,MERGED,REMOTE
+[sendemail]
+ smtpserver = smtp.fastmail.com
+ smtpuser = marcc@fastmail.fr
+ smtpencryption = ssl
+ smtpserverport = 465
+[pull]
+ rebase = true
+[color "diff"]
+ meta = blue
+ old = red
+ new = green
+[log]
+ date = relative
+[format]
+ pretty = oneline
+ signoff = true
+[credential]
+ helper = store
+[push]
+ autoSetupRemote = true
+[commit]
+ gpgsign = true
+ verbose = true
+[init]
+ defaultBranch = main
+[grep]
+ lineNumber = true
+[help]
+ autocorrect = prompt
diff --git a/kshrc b/kshrc
new file mode 100644
index 0000000..da9c5a0
--- /dev/null
+++ b/kshrc
@@ -0,0 +1,19 @@
+export GPG_TTY=/dev/pts/0
+export HISTFILE="$HOME/.history"
+alias grep="ugrep --exclude-dir=node-modules --exclude-dir=_build"
+alias e="$EDITOR"
+alias g="git"
+#alias sdf="sshpass -p $(pass sdf) ssh mccd@tty.sdf.org"
+alias de='eval "$(direnv export bash)"'
+alias f-commit="fossil commit && fossil git export"
+alias jrnl='stitch -t :journal:'
+
+PS1='${PWD##*/} '
+
+set -o emacs
+if [ -x "$(command -v opam)" ]; then
+ eval $(opam env)
+fi
+
+date
+apm
diff --git a/local/bin/pfetch b/local/bin/pfetch
new file mode 100755
index 0000000..21673dd
--- /dev/null
+++ b/local/bin/pfetch
@@ -0,0 +1,1958 @@
+#!/bin/sh
+#
+# pfetch - Simple POSIX sh fetch script.
+
+# Wrapper around all escape sequences used by pfetch to allow for
+# greater control over which sequences are used (if any at all).
+esc() {
+ case $1 in
+ CUU) e="${esc_c}[${2}A" ;; # cursor up
+ CUD) e="${esc_c}[${2}B" ;; # cursor down
+ CUF) e="${esc_c}[${2}C" ;; # cursor right
+ CUB) e="${esc_c}[${2}D" ;; # cursor left
+
+ # text formatting
+ SGR)
+ case ${PF_COLOR:=1} in
+ (1)
+ e="${esc_c}[${2}m"
+ ;;
+
+ (0)
+ # colors disabled
+ e=
+ ;;
+ esac
+ ;;
+
+ # line wrap
+ DECAWM)
+ case $TERM in
+ (dumb | minix | cons25)
+ # not supported
+ e=
+ ;;
+
+ (*)
+ e="${esc_c}[?7${2}"
+ ;;
+ esac
+ ;;
+ esac
+}
+
+# Print a sequence to the terminal.
+esc_p() {
+ esc "$@"
+ printf '%s' "$e"
+}
+
+# This is just a simple wrapper around 'command -v' to avoid
+# spamming '>/dev/null' throughout this function. This also guards
+# against aliases and functions.
+has() {
+ _cmd=$(command -v "$1") 2>/dev/null || return 1
+ [ -x "$_cmd" ] || return 1
+}
+
+log() {
+ # The 'log()' function handles the printing of information.
+ # In 'pfetch' (and 'neofetch'!) the printing of the ascii art and info
+ # happen independently of each other.
+ #
+ # The size of the ascii art is stored and the ascii is printed first.
+ # Once the ascii is printed, the cursor is located right below the art
+ # (See marker $[1]).
+ #
+ # Using the stored ascii size, the cursor is then moved to marker $[2].
+ # This is simply a cursor up escape sequence using the "height" of the
+ # ascii art.
+ #
+ # 'log()' then moves the cursor to the right the "width" of the ascii art
+ # with an additional amount of padding to add a gap between the art and
+ # the information (See marker $[3]).
+ #
+ # When 'log()' has executed, the cursor is then located at marker $[4].
+ # When 'log()' is run a second time, the next line of information is
+ # printed, moving the cursor to marker $[5].
+ #
+ # Markers $[4] and $[5] repeat all the way down through the ascii art
+ # until there is no more information left to print.
+ #
+ # Every time 'log()' is called the script keeps track of how many lines
+ # were printed. When printing is complete the cursor is then manually
+ # placed below the information and the art according to the "heights"
+ # of both.
+ #
+ # The math is simple: move cursor down $((ascii_height - info_height)).
+ # If the aim is to move the cursor from marker $[5] to marker $[6],
+ # plus the ascii height is 8 while the info height is 2 it'd be a move
+ # of 6 lines downwards.
+ #
+ # However, if the information printed is "taller" (takes up more lines)
+ # than the ascii art, the cursor isn't moved at all!
+ #
+ # Once the cursor is at marker $[6], the script exits. This is the gist
+ # of how this "dynamic" printing and layout works.
+ #
+ # This method allows ascii art to be stored without markers for info
+ # and it allows for easy swapping of info order and amount.
+ #
+ # $[2] ___ $[3] goldie@KISS
+ # $[4](.· | $[5] os KISS Linux
+ # (<> |
+ # / __ \
+ # ( / \ /|
+ # _/\ __)/_)
+ # \/-____\/
+ # $[1]
+ #
+ # $[6] /home/goldie $
+
+ # End here if no data was found.
+ [ "$2" ] || return
+
+ # Store the values of '$1' and '$3' as we reset the argument list below.
+ name=$1
+ use_seperator=$3
+
+ # Use 'set --' as a means of stripping all leading and trailing
+ # white-space from the info string. This also normalizes all
+ # white-space inside of the string.
+ #
+ # Disable the shellcheck warning for word-splitting
+ # as it's safe and intended ('set -f' disables globbing).
+ # shellcheck disable=2046,2086
+ {
+ set -f
+ set +f -- $2
+ info=$*
+ }
+
+ # Move the cursor to the right, the width of the ascii art with an
+ # additional gap for text spacing.
+ esc_p CUF "$ascii_width"
+
+ # Print the info name and color the text.
+ esc_p SGR "3${PF_COL1-4}";
+ esc_p SGR 1
+ printf '%s' "$name"
+ esc_p SGR 0
+
+ # Print the info name and info data separator, if applicable.
+ [ "$use_seperator" ] || printf %s "$PF_SEP"
+
+ # Move the cursor backward the length of the *current* info name and
+ # then move it forwards the length of the *longest* info name. This
+ # aligns each info data line.
+ esc_p CUB "${#name}"
+ esc_p CUF "${PF_ALIGN:-$info_length}"
+
+ # Print the info data, color it and strip all leading whitespace
+ # from the string.
+ esc_p SGR "3${PF_COL2-9}"
+ printf '%s' "$info"
+ esc_p SGR 0
+ printf '\n'
+
+ # Keep track of the number of times 'log()' has been run.
+ info_height=$((${info_height:-0} + 1))
+}
+
+get_title() {
+ # Username is retrieved by first checking '$USER' with a fallback
+ # to the 'id -un' command.
+ user=${USER:-$(id -un)}
+
+ # Hostname is retrieved by first checking '$HOSTNAME' with a fallback
+ # to the 'hostname' command.
+ #
+ # Disable the warning about '$HOSTNAME' being undefined in POSIX sh as
+ # the intention for using it is allowing the user to overwrite the
+ # value on invocation.
+ # shellcheck disable=3028,2039
+ hostname=${HOSTNAME:-${hostname:-$(hostname)}}
+
+ # If the hostname is still not found, fallback to the contents of the
+ # /etc/hostname file.
+ [ "$hostname" ] || read -r hostname < /etc/hostname
+
+ # Add escape sequences for coloring to user and host name. As we embed
+ # them directly in the arguments passed to log(), we cannot use esc_p().
+ esc SGR 1
+ user=$e$user
+ esc SGR "3${PF_COL3:-1}"
+ user=$e$user
+ esc SGR 1
+ user=$user$e
+ esc SGR 1
+ hostname=$e$hostname
+ esc SGR "3${PF_COL3:-1}"
+ hostname=$e$hostname
+
+ log "${user}@${hostname}" " " " " >&6
+}
+
+get_os() {
+ # This function is called twice, once to detect the distribution name
+ # for the purposes of picking an ascii art early and secondly to display
+ # the distribution name in the info output (if enabled).
+ #
+ # On first run, this function displays _nothing_, only on the second
+ # invocation is 'log()' called.
+ [ "$distro" ] && {
+ log os "$distro" >&6
+ return
+ }
+
+ case $os in
+ (Linux*)
+ # Some Linux distributions (which are based on others)
+ # fail to identify as they **do not** change the upstream
+ # distribution's identification packages or files.
+ #
+ # It is senseless to add a special case in the code for
+ # each and every distribution (which _is_ technically no
+ # different from what it is based on) as they're either too
+ # lazy to modify upstream's identification files or they
+ # don't have the know-how (or means) to ship their own
+ # lsb-release package.
+ #
+ # This causes users to think there's a bug in system detection
+ # tools like neofetch or pfetch when they technically *do*
+ # function correctly.
+ #
+ # Exceptions are made for distributions which are independent,
+ # not based on another distribution or follow different
+ # standards.
+ #
+ # This applies only to distributions which follow the standard
+ # by shipping unmodified identification files and packages
+ # from their respective upstreams.
+ if has lsb_release; then
+ distro=$(lsb_release -sd)
+
+ # Android detection works by checking for the existence of
+ # the follow two directories. I don't think there's a simpler
+ # method than this.
+ elif [ -d /system/app ] && [ -d /system/priv-app ]; then
+ distro="Android $(getprop ro.build.version.release)"
+
+ elif [ -f /etc/os-release ]; then
+ # This used to be a simple '. /etc/os-release' but I believe
+ # this is insecure as we blindly executed whatever was in the
+ # file. This parser instead simply handles 'key=val', treating
+ # the file contents as plain-text.
+ while IFS='=' read -r key val; do
+ case $key in
+ (NAME)
+ distro="$(echo $val | tr '[:upper:]' '[:lower:]')"
+ ;;
+ esac
+ done < /etc/os-release
+
+ else
+ # Special cases for (independent) distributions which
+ # don't follow any os-release/lsb standards whatsoever.
+ has crux && distro=$(crux)
+ has guix && distro='Guix System'
+ fi
+
+ # 'os-release' and 'lsb_release' sometimes add quotes
+ # around the distribution name, strip them.
+ distro=${distro##[\"\']}
+ distro=${distro%%[\"\']}
+
+ # Check to see if we're running Bedrock Linux which is
+ # very unique. This simply checks to see if the user's
+ # PATH contains a Bedrock specific value.
+ case $PATH in
+ (*/bedrock/cross/*)
+ distro='Bedrock Linux'
+ ;;
+ esac
+
+ # Check to see if Linux is running in Windows 10 under
+ # WSL1 (Windows subsystem for Linux [version 1]) and
+ # append a string accordingly.
+ #
+ # If the kernel version string ends in "-Microsoft",
+ # we're very likely running under Windows 10 in WSL1.
+ if [ "$WSLENV" ]; then
+ distro="${distro}${WSLENV+ on Windows 10 [WSL2]}"
+
+ # Check to see if Linux is running in Windows 10 under
+ # WSL2 (Windows subsystem for Linux [version 2]) and
+ # append a string accordingly.
+ #
+ # This checks to see if '$WSLENV' is defined. This
+ # appends the Windows 10 string even if '$WSLENV' is
+ # empty. We only need to check that is has been _exported_.
+ elif [ -z "${kernel%%*-Microsoft}" ]; then
+ distro="$distro on Windows 10 [WSL1]"
+ fi
+ ;;
+
+ (Darwin*)
+ # Parse the SystemVersion.plist file to grab the macOS
+ # version. The file is in the following format:
+ #
+ # <key>ProductVersion</key>
+ # <string>10.14.6</string>
+ #
+ # 'IFS' is set to '<>' to enable splitting between the
+ # keys and a second 'read' is used to operate on the
+ # next line directly after a match.
+ #
+ # '_' is used to nullify a field. '_ _ line _' basically
+ # says "populate $line with the third field's contents".
+ while IFS='<>' read -r _ _ line _; do
+ case $line in
+ # Match 'ProductVersion' and read the next line
+ # directly as it contains the key's value.
+ ProductVersion)
+ IFS='<>' read -r _ _ mac_version _
+ continue
+ ;;
+
+ ProductName)
+ IFS='<>' read -r _ _ mac_product _
+ continue
+ ;;
+ esac
+ done < /System/Library/CoreServices/SystemVersion.plist
+
+ # Use the ProductVersion to determine which macOS/OS X codename
+ # the system has. As far as I'm aware there's no "dynamic" way
+ # of grabbing this information.
+ case $mac_version in
+ (10.4*) distro='Mac OS X Tiger' ;;
+ (10.5*) distro='Mac OS X Leopard' ;;
+ (10.6*) distro='Mac OS X Snow Leopard' ;;
+ (10.7*) distro='Mac OS X Lion' ;;
+ (10.8*) distro='OS X Mountain Lion' ;;
+ (10.9*) distro='OS X Mavericks' ;;
+ (10.10*) distro='OS X Yosemite' ;;
+ (10.11*) distro='OS X El Capitan' ;;
+ (10.12*) distro='macOS Sierra' ;;
+ (10.13*) distro='macOS High Sierra' ;;
+ (10.14*) distro='macOS Mojave' ;;
+ (10.15*) distro='macOS Catalina' ;;
+ (11*) distro='macOS Big Sur' ;;
+ (12*) distro='macOS Monterey' ;;
+ (*) distro='macOS' ;;
+ esac
+
+ # Use the ProductName to determine if we're running in iOS.
+ case $mac_product in
+ (iP*) distro='iOS' ;;
+ esac
+
+ distro="$distro $mac_version"
+ ;;
+
+ (Haiku)
+ # Haiku uses 'uname -v' for version information
+ # instead of 'uname -r' which only prints '1'.
+ distro=$(uname -sv)
+ ;;
+
+ (Minix|DragonFly)
+ distro="$os $kernel"
+
+ # Minix and DragonFly don't support the escape
+ # sequences used, clear the exit trap.
+ trap '' EXIT
+ ;;
+
+ (SunOS)
+ # Grab the first line of the '/etc/release' file
+ # discarding everything after '('.
+ IFS='(' read -r distro _ < /etc/release
+ ;;
+
+ (OpenBSD*)
+ # Show the OpenBSD version type (current if present).
+ # kern.version=OpenBSD 6.6-current (GENERIC.MP) ...
+ IFS=' =' read -r _ distro openbsd_ver _ <<-EOF
+ $(sysctl kern.version)
+ EOF
+
+ distro="$distro $openbsd_ver"
+ ;;
+
+ (FreeBSD)
+ distro="$(echo $os | tr '[:upper:]' '[:lower:]')"
+ ;;
+
+ (*)
+ # Catch all to ensure '$distro' is never blank.
+ # This also handles the BSDs.
+ distro="$os $kernel"
+ ;;
+ esac
+}
+
+get_kernel() {
+ case $os in
+ # Don't print kernel output on some systems as the
+ # OS name includes it.
+ (*BSD*|Haiku|Minix)
+ return
+ ;;
+ esac
+
+ # '$kernel' is the cached output of 'uname -r'.
+ log kernel "$kernel" >&6
+}
+
+get_host() {
+ case $os in
+ (Linux*)
+ # Despite what these files are called, version doesn't
+ # always contain the version nor does name always contain
+ # the name.
+ read -r name < /sys/devices/virtual/dmi/id/product_name
+ read -r version < /sys/devices/virtual/dmi/id/product_version
+ read -r model < /sys/firmware/devicetree/base/model
+
+ host="$name $version $model"
+ ;;
+
+ (Darwin* | FreeBSD* | DragonFly*)
+ host=$(sysctl -n hw.model)
+ ;;
+
+ (NetBSD*)
+ host=$(sysctl -n machdep.dmi.system-vendor \
+ machdep.dmi.system-product)
+ ;;
+
+ (OpenBSD*)
+ host=$(sysctl -n hw.version)
+ ;;
+
+ (*BSD* | Minix)
+ host=$(sysctl -n hw.vendor hw.product)
+ ;;
+ esac
+
+ # Turn the host string into an argument list so we can iterate
+ # over it and remove OEM strings and other information which
+ # shouldn't be displayed.
+ #
+ # Disable the shellcheck warning for word-splitting
+ # as it's safe and intended ('set -f' disables globbing).
+ # shellcheck disable=2046,2086
+ {
+ set -f
+ set +f -- $host
+ host=
+ }
+
+ # Iterate over the host string word by word as a means of stripping
+ # unwanted and OEM information from the string as a whole.
+ #
+ # This could have been implemented using a long 'sed' command with
+ # a list of word replacements, however I want to show that something
+ # like this is possible in pure sh.
+ #
+ # This string reconstruction is needed as some OEMs either leave the
+ # identification information as "To be filled by OEM", "Default",
+ # "undefined" etc and we shouldn't print this to the screen.
+ for word do
+ # This works by reconstructing the string by excluding words
+ # found in the "blacklist" below. Only non-matches are appended
+ # to the final host string.
+ case $word in
+ (To | [Bb]e | [Ff]illed | [Bb]y | O.E.M. | OEM |\
+ Not | Applicable | Specified | System | Product | Name |\
+ Version | Undefined | Default | string | INVALID | � | os |\
+ Type1ProductConfigId )
+ continue
+ ;;
+ esac
+
+ host="$host$word "
+ done
+
+ # '$arch' is the cached output from 'uname -m'.
+ log host "${host:-$arch}" >&6
+}
+
+get_uptime() {
+ # Uptime works by retrieving the data in total seconds and then
+ # converting that data into days, hours and minutes using simple
+ # math.
+ case $os in
+ (Linux* | Minix* | SerenityOS*)
+ IFS=. read -r s _ < /proc/uptime
+ ;;
+
+ (Darwin* | *BSD* | DragonFly*)
+ s=$(sysctl -n kern.boottime)
+
+ # Extract the uptime in seconds from the following output:
+ # [...] { sec = 1271934886, usec = 667779 } Thu Apr 22 12:14:46 2010
+ s=${s#*=}
+ s=${s%,*}
+
+ # The uptime format from 'sysctl' needs to be subtracted from
+ # the current time in seconds.
+ s=$(($(date +%s) - s))
+ ;;
+
+ (Haiku)
+ # The boot time is returned in microseconds, convert it to
+ # regular seconds.
+ s=$(($(system_time) / 1000000))
+ ;;
+
+ (SunOS)
+ # Split the output of 'kstat' on '.' and any white-space
+ # which exists in the command output.
+ #
+ # The output is as follows:
+ # unix:0:system_misc:snaptime 14809.906993005
+ #
+ # The parser extracts: ^^^^^
+ IFS=' .' read -r _ s _ <<-EOF
+ $(kstat -p unix:0:system_misc:snaptime)
+ EOF
+ ;;
+
+ (IRIX)
+ # Grab the uptime in a pretty format. Usually,
+ # 00:00:00 from the 'ps' command.
+ t=$(LC_ALL=POSIX ps -o etime= -p 1)
+
+ # Split the pretty output into days or hours
+ # based on the uptime.
+ case $t in
+ (*-*) d=${t%%-*} t=${t#*-} ;;
+ (*:*:*) h=${t%%:*} t=${t#*:} ;;
+ esac
+
+ h=${h#0} t=${t#0}
+
+ # Convert the split pretty fields back into
+ # seconds so we may re-convert them to our format.
+ s=$((${d:-0}*86400 + ${h:-0}*3600 + ${t%%:*}*60 + ${t#*:}))
+ ;;
+ esac
+
+ # Convert the uptime from seconds into days, hours and minutes.
+ d=$((s / 60 / 60 / 24))
+ h=$((s / 60 / 60 % 24))
+ m=$((s / 60 % 60))
+
+ # Only append days, hours and minutes if they're non-zero.
+ case "$d" in ([!0]*) uptime="${uptime}${d}d "; esac
+ case "$h" in ([!0]*) uptime="${uptime}${h}h "; esac
+ case "$m" in ([!0]*) uptime="${uptime}${m}m "; esac
+
+ log uptime "${uptime:-0m}" >&6
+}
+
+get_pkgs() {
+ # This works by first checking for which package managers are
+ # installed and finally by printing each package manager's
+ # package list with each package one per line.
+ #
+ # The output from this is then piped to 'wc -l' to count each
+ # line, giving us the total package count of whatever package
+ # managers are installed.
+ packages=$(
+ case $os in
+ (Linux*)
+ # Commands which print packages one per line.
+ has bonsai && bonsai list
+ has crux && pkginfo -i
+ has pacman-key && pacman -Qq
+ has dpkg && dpkg-query -f '.\n' -W
+ has rpm && rpm -qa
+ has xbps-query && xbps-query -l
+ has apk && apk info
+ has guix && guix package --list-installed
+ has opkg && opkg list-installed
+
+ # Directories containing packages.
+ has kiss && printf '%s\n' /var/db/kiss/installed/*/
+ has cpt-list && printf '%s\n' /var/db/cpt/installed/*/
+ has brew && printf '%s\n' "$(brew --cellar)/"*
+ has emerge && printf '%s\n' /var/db/pkg/*/*/
+ has pkgtool && printf '%s\n' /var/log/packages/*
+ has eopkg && printf '%s\n' /var/lib/eopkg/package/*
+
+ # 'nix' requires two commands.
+ has nix-store && {
+ nix-store -q --requisites /run/current-system/sw
+ nix-store -q --requisites ~/.nix-profile
+ }
+ ;;
+
+ (Darwin*)
+ # Commands which print packages one per line.
+ has pkgin && pkgin list
+ has dpkg && dpkg-query -f '.\n' -W
+
+ # Directories containing packages.
+ has brew && printf '%s\n' /usr/local/Cellar/*
+
+ # 'port' prints a single line of output to 'stdout'
+ # when no packages are installed and exits with
+ # success causing a false-positive of 1 package
+ # installed.
+ #
+ # 'port' should really exit with a non-zero code
+ # in this case to allow scripts to cleanly handle
+ # this behavior.
+ has port && {
+ pkg_list=$(port installed)
+
+ case "$pkg_list" in
+ ("No ports are installed.")
+ # do nothing
+ ;;
+
+ (*)
+ printf '%s\n' "$pkg_list"
+ ;;
+ esac
+ }
+ ;;
+
+ (FreeBSD*|DragonFly*)
+ pkg info
+ ;;
+
+ (OpenBSD*)
+ printf '%s\n' /var/db/pkg/*/
+ ;;
+
+ (NetBSD*)
+ pkg_info
+ ;;
+
+ (Haiku)
+ printf '%s\n' /boot/system/package-links/*
+ ;;
+
+ (Minix)
+ printf '%s\n' /usr/pkg/var/db/pkg/*/
+ ;;
+
+ (SunOS)
+ has pkginfo && pkginfo -i
+ has pkg && pkg list
+ ;;
+
+ (IRIX)
+ versions -b
+ ;;
+
+ (SerenityOS)
+ while IFS=" " read -r type _; do
+ [ "$type" != dependency ] &&
+ printf "\n"
+ done < /usr/Ports/packages.db
+ ;;
+ esac | wc -l
+ )
+
+ # 'wc -l' can have leading and/or trailing whitespace
+ # depending on the implementation, so strip them.
+ # Procedure explained at https://github.com/dylanaraps/pure-sh-bible
+ # (trim-leading-and-trailing-white-space-from-string)
+ packages=${packages#"${packages%%[![:space:]]*}"}
+ packages=${packages%"${packages##*[![:space:]]}"}
+
+ case $os in
+ # IRIX's package manager adds 3 lines of extra
+ # output which we must account for here.
+ (IRIX)
+ packages=$((packages - 3))
+ ;;
+
+ # OpenBSD's wc prints whitespace before the output
+ # which needs to be stripped.
+ (OpenBSD)
+ packages=$((packages))
+ ;;
+ esac
+
+ case $packages in
+ (1?*|[2-9]*)
+ log pkgs "$packages" >&6
+ ;;
+ esac
+}
+
+get_memory() {
+ case $os in
+ # Used memory is calculated using the following "formula":
+ # MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable
+ # Source: https://github.com/KittyKatt/screenFetch/issues/386
+ (Linux*)
+ # Parse the '/proc/meminfo' file splitting on ':' and 'k'.
+ # The format of the file is 'key: 000kB' and an additional
+ # split is used on 'k' to filter out 'kB'.
+ while IFS=':k ' read -r key val _; do
+ case $key in
+ (MemTotal)
+ mem_used=$((mem_used + val))
+ mem_full=$val
+ ;;
+
+ (Shmem)
+ mem_used=$((mem_used + val))
+ ;;
+
+ (MemFree | Buffers | Cached | SReclaimable)
+ mem_used=$((mem_used - val))
+ ;;
+
+ # If detected this will be used over the above calculation
+ # for mem_used. Available since Linux 3.14rc.
+ # See kernel commit 34e431b0ae398fc54ea69ff85ec700722c9da773
+ (MemAvailable)
+ mem_avail=$val
+ ;;
+ esac
+ done < /proc/meminfo
+
+ case $mem_avail in
+ (*[0-9]*)
+ mem_used=$(((mem_full - mem_avail) / 1024))
+ ;;
+
+ *)
+ mem_used=$((mem_used / 1024))
+ ;;
+ esac
+
+ mem_full=$((mem_full / 1024))
+ ;;
+
+ # Used memory is calculated using the following "formula":
+ # (wired + active + occupied) * 4 / 1024
+ (Darwin*)
+ mem_full=$(($(sysctl -n hw.memsize) / 1024 / 1024))
+
+ # Parse the 'vmstat' file splitting on ':' and '.'.
+ # The format of the file is 'key: 000.' and an additional
+ # split is used on '.' to filter it out.
+ while IFS=:. read -r key val; do
+ case $key in
+ (*' wired'*|*' active'*|*' occupied'*)
+ mem_used=$((mem_used + ${val:-0}))
+ ;;
+ esac
+
+ # Using '<<-EOF' is the only way to loop over a command's
+ # output without the use of a pipe ('|').
+ # This ensures that any variables defined in the while loop
+ # are still accessible in the script.
+ done <<-EOF
+ $(vm_stat)
+ EOF
+
+ mem_used=$((mem_used * 4 / 1024))
+ ;;
+
+ (OpenBSD*)
+ mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))
+
+ # This is a really simpler parser for 'vmstat' which grabs
+ # the used memory amount in a lazy way. 'vmstat' prints 3
+ # lines of output with the needed value being stored in the
+ # final line.
+ #
+ # This loop simply grabs the 3rd element of each line until
+ # the EOF is reached. Each line overwrites the value of the
+ # previous one so we're left with what we wanted. This isn't
+ # slow as only 3 lines are parsed.
+ while read -r _ _ line _; do
+ mem_used=${line%%M}
+
+ # Using '<<-EOF' is the only way to loop over a command's
+ # output without the use of a pipe ('|').
+ # This ensures that any variables defined in the while loop
+ # are still accessible in the script.
+ done <<-EOF
+ $(vmstat)
+ EOF
+ ;;
+
+ # Used memory is calculated using the following "formula":
+ # mem_full - ((inactive + free + cache) * page_size / 1024)
+ (FreeBSD*|DragonFly*)
+ mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))
+
+ # Use 'set --' to store the output of the command in the
+ # argument list. POSIX sh has no arrays but this is close enough.
+ #
+ # Disable the shellcheck warning for word-splitting
+ # as it's safe and intended ('set -f' disables globbing).
+ # shellcheck disable=2046
+ {
+ set -f
+ set +f -- $(sysctl -n hw.pagesize \
+ vm.stats.vm.v_inactive_count \
+ vm.stats.vm.v_free_count \
+ vm.stats.vm.v_cache_count)
+ }
+
+ # Calculate the amount of used memory.
+ # $1: hw.pagesize
+ # $2: vm.stats.vm.v_inactive_count
+ # $3: vm.stats.vm.v_free_count
+ # $4: vm.stats.vm.v_cache_count
+ mem_used=$((mem_full - (($2 + $3 + $4) * $1 / 1024 / 1024)))
+ ;;
+
+ (NetBSD*)
+ mem_full=$(($(sysctl -n hw.physmem64) / 1024 / 1024))
+
+ # NetBSD implements a lot of the Linux '/proc' filesystem,
+ # this uses the same parser as the Linux memory detection.
+ while IFS=':k ' read -r key val _; do
+ case $key in
+ (MemFree)
+ mem_free=$((val / 1024))
+ break
+ ;;
+ esac
+ done < /proc/meminfo
+
+ mem_used=$((mem_full - mem_free))
+ ;;
+
+ (Haiku)
+ # Read the first line of 'sysinfo -mem' splitting on
+ # '(', ' ', and ')'. The needed information is then
+ # stored in the 5th and 7th elements. Using '_' "consumes"
+ # an element allowing us to proceed to the next one.
+ #
+ # The parsed format is as follows:
+ # 3501142016 bytes free (used/max 792645632 / 4293787648)
+ IFS='( )' read -r _ _ _ _ mem_used _ mem_full <<-EOF
+ $(sysinfo -mem)
+ EOF
+
+ mem_used=$((mem_used / 1024 / 1024))
+ mem_full=$((mem_full / 1024 / 1024))
+ ;;
+
+ (Minix)
+ # Minix includes the '/proc' filesystem though the format
+ # differs from Linux. The '/proc/meminfo' file is only a
+ # single line with space separated elements and elements
+ # 2 and 3 contain the total and free memory numbers.
+ read -r _ mem_full mem_free _ < /proc/meminfo
+
+ mem_used=$(((mem_full - mem_free) / 1024))
+ mem_full=$(( mem_full / 1024))
+ ;;
+
+ (SunOS)
+ hw_pagesize=$(pagesize)
+
+ # 'kstat' outputs memory in the following format:
+ # unix:0:system_pages:pagestotal 1046397
+ # unix:0:system_pages:pagesfree 885018
+ #
+ # This simply uses the first "element" (white-space
+ # separated) as the key and the second element as the
+ # value.
+ #
+ # A variable is then assigned based on the key.
+ while read -r key val; do
+ case $key in
+ (*total)
+ pages_full=$val
+ ;;
+
+ (*free)
+ pages_free=$val
+ ;;
+ esac
+ done <<-EOF
+ $(kstat -p unix:0:system_pages:pagestotal \
+ unix:0:system_pages:pagesfree)
+ EOF
+
+ mem_full=$((pages_full * hw_pagesize / 1024 / 1024))
+ mem_free=$((pages_free * hw_pagesize / 1024 / 1024))
+ mem_used=$((mem_full - mem_free))
+ ;;
+
+ (IRIX)
+ # Read the memory information from the 'top' command. Parse
+ # and split each line until we reach the line starting with
+ # "Memory".
+ #
+ # Example output: Memory: 160M max, 147M avail, .....
+ while IFS=' :' read -r label mem_full _ mem_free _; do
+ case $label in
+ (Memory)
+ mem_full=${mem_full%M}
+ mem_free=${mem_free%M}
+ break
+ ;;
+ esac
+ done <<-EOF
+ $(top -n)
+ EOF
+
+ mem_used=$((mem_full - mem_free))
+ ;;
+
+ (SerenityOS)
+ IFS='{}' read -r _ memstat _ < /proc/memstat
+
+ set -f -- "$IFS"
+ IFS=,
+
+ for pair in $memstat; do
+ case $pair in
+ (*user_physical_allocated*)
+ mem_used=${pair##*:}
+ ;;
+
+ (*user_physical_available*)
+ mem_free=${pair##*:}
+ ;;
+ esac
+ done
+
+ IFS=$1
+ set +f --
+
+ mem_used=$((mem_used * 4096 / 1024 / 1024))
+ mem_free=$((mem_free * 4096 / 1024 / 1024))
+
+ mem_full=$((mem_used + mem_free))
+ ;;
+ esac
+
+ log memory "${mem_used:-?}M / ${mem_full:-?}M" >&6
+}
+
+get_wm() {
+ case $os in
+ (Darwin*)
+ # Don't display window manager on macOS.
+ ;;
+
+ (*)
+ # xprop can be used to grab the window manager's properties
+ # which contains the window manager's name under '_NET_WM_NAME'.
+ #
+ # The upside to using 'xprop' is that you don't need to hardcode
+ # a list of known window manager names. The downside is that
+ # not all window managers conform to setting the '_NET_WM_NAME'
+ # atom..
+ #
+ # List of window managers which fail to set the name atom:
+ # catwm, fvwm, dwm, 2bwm, monster, wmaker and sowm [mine! ;)].
+ #
+ # The final downside to this approach is that it does _not_
+ # support Wayland environments. The only solution which supports
+ # Wayland is the 'ps' parsing mentioned below.
+ #
+ # A more naive implementation is to parse the last line of
+ # '~/.xinitrc' to extract the second white-space separated
+ # element.
+ #
+ # The issue with an approach like this is that this line data
+ # does not always equate to the name of the window manager and
+ # could in theory be _anything_.
+ #
+ # This also fails when the user launches xorg through a display
+ # manager or other means.
+ #
+ #
+ # Another naive solution is to parse 'ps' with a hardcoded list
+ # of window managers to detect the current window manager (based
+ # on what is running).
+ #
+ # The issue with this approach is the need to hardcode and
+ # maintain a list of known window managers.
+ #
+ # Another issue is that process names do not always equate to
+ # the name of the window manager. False-positives can happen too.
+ #
+ # This is the only solution which supports Wayland based
+ # environments sadly. It'd be nice if some kind of standard were
+ # established to identify Wayland environments.
+ #
+ # pfetch's goal is to remain _simple_, if you'd like a "full"
+ # implementation of window manager detection use 'neofetch'.
+ #
+ # Neofetch use a combination of 'xprop' and 'ps' parsing to
+ # support all window managers (including non-conforming and
+ # Wayland) though it's a lot more complicated!
+
+ # Don't display window manager if X isn't running.
+ [ "$DISPLAY" ] || return
+
+ # This is a two pass call to xprop. One call to get the window
+ # manager's ID and another to print its properties.
+ has xprop && {
+ # The output of the ID command is as follows:
+ # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
+ #
+ # To extract the ID, everything before the last space
+ # is removed.
+ id=$(xprop -root -notype _NET_SUPPORTING_WM_CHECK)
+ id=${id##* }
+
+ # The output of the property command is as follows:
+ # _NAME 8t
+ # _NET_WM_PID = 252
+ # _NET_WM_NAME = "bspwm"
+ # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
+ # WM_CLASS = "wm", "Bspwm"
+ #
+ # To extract the name, everything before '_NET_WM_NAME = \"'
+ # is removed and everything after the next '"' is removed.
+ wm=$(xprop -id "$id" -notype -len 7 -f _NET_WM_NAME 8t)
+ }
+
+ # Handle cases of a window manager _not_ populating the
+ # '_NET_WM_NAME' atom. Display nothing in this case.
+ case $wm in
+ (*'_NET_WM_NAME = '*)
+ wm=${wm##*_NET_WM_NAME = \"}
+ wm=${wm%%\"*}
+ ;;
+
+ (*)
+ # Fallback to checking the process list
+ # for the select few window managers which
+ # don't set '_NET_WM_NAME'.
+ while read -r ps_line; do
+ case $ps_line in
+ (*catwm*) wm=catwm ;;
+ (*fvwm*) wm=fvwm ;;
+ (*dwm*) wm=dwm ;;
+ (*2bwm*) wm=2bwm ;;
+ (*monsterwm*) wm=monsterwm ;;
+ (*wmaker*) wm='Window Maker' ;;
+ (*sowm*) wm=sowm ;;
+ (*penrose*) wm=penrose ;;
+ esac
+ done <<-EOF
+ $(ps x)
+ EOF
+ ;;
+ esac
+ ;;
+ esac
+
+ log wm "cwm" >&6
+}
+
+
+get_de() {
+ # This only supports Xorg related desktop environments though
+ # this is fine as knowing the desktop environment on Windows,
+ # macOS etc is useless (they'll always report the same value).
+ #
+ # Display the value of '$XDG_CURRENT_DESKTOP', if it's empty,
+ # display the value of '$DESKTOP_SESSION'.
+ log de "${XDG_CURRENT_DESKTOP:-$DESKTOP_SESSION}" >&6
+}
+
+get_shell() {
+ # Display the basename of the '$SHELL' environment variable.
+ log sh "${SHELL##*/}" >&6
+}
+
+get_editor() {
+ # Display the value of '$VISUAL', if it's empty, display the
+ # value of '$EDITOR'.
+ editor=${VISUAL:-"$EDITOR"}
+
+ log ed "${editor##*/}" >&6
+}
+
+get_palette() {
+ # Print the first 8 terminal colors. This uses the existing
+ # sequences to change text color with a sequence prepended
+ # to reverse the foreground and background colors.
+ #
+ # This allows us to save hardcoding a second set of sequences
+ # for background colors.
+ #
+ # False positive.
+ # shellcheck disable=2154
+ {
+ esc SGR 7
+ palette="$e$c1 $c1 $c2 $c2 $c3 $c3 $c4 $c4 $c5 $c5 $c6 $c6 "
+ esc SGR 0
+ palette="$palette$e"
+ }
+
+ # Print the palette with a new-line before and afterwards but no seperator.
+ printf '\n' >&6
+ log "$palette
+ " " " " " >&6
+}
+
+get_ascii() {
+ # This is a simple function to read the contents of
+ # an ascii file from 'stdin'. It allows for the use
+ # of '<<-EOF' to prevent the break in indentation in
+ # this source code.
+ #
+ # This function also sets the text colors according
+ # to the ascii color.
+ read_ascii() {
+ # 'PF_COL1': Set the info name color according to ascii color.
+ # 'PF_COL3': Set the title color to some other color. ¯\_(ツ)_/¯
+ PF_COL1=${PF_COL1:-${1:-7}}
+ PF_COL3=${PF_COL3:-$((${1:-7}%8+1))}
+
+ # POSIX sh has no 'var+=' so 'var=${var}append' is used. What's
+ # interesting is that 'var+=' _is_ supported inside '$(())'
+ # (arithmetic) though there's no support for 'var++/var--'.
+ #
+ # There is also no $'\n' to add a "literal"(?) newline to the
+ # string. The simplest workaround being to break the line inside
+ # the string (though this has the caveat of breaking indentation).
+ while IFS= read -r line; do
+ ascii="$ascii$line
+"
+ done
+ }
+
+ # This checks for ascii art in the following order:
+ # '$1': Argument given to 'get_ascii()' directly.
+ # '$PF_ASCII': Environment variable set by user.
+ # '$distro': The detected distribution name.
+ # '$os': The name of the operating system/kernel.
+ #
+ # NOTE: Each ascii art below is indented using tabs, this
+ # allows indentation to continue naturally despite
+ # the use of '<<-EOF'.
+ #
+ # False positive.
+ # shellcheck disable=2154
+ case ${1:-${PF_ASCII:-${distro:-$os}}} in
+ ([Aa]lpine*)
+ read_ascii 4 <<-EOF
+ ${c4} /\\ /\\
+ /${c7}/ ${c4}\\ \\
+ /${c7}/ ${c4}\\ \\
+ /${c7}// ${c4}\\ \\
+ ${c7}// ${c4}\\ \\
+ ${c4}\\
+ EOF
+ ;;
+
+ ([Aa]ndroid*)
+ read_ascii 2 <<-EOF
+ ${c2} ;, ,;
+ ${c2} ';,.-----.,;'
+ ${c2} ,' ',
+ ${c2} / O O \\
+ ${c2}| |
+ ${c2}'-----------------'
+ EOF
+ ;;
+
+ ([Aa]rch*)
+ read_ascii 4 <<-EOF
+ ${c6} /\\
+ ${c6} / \\
+ ${c6} /\\ \\
+ ${c4} / \\
+ ${c4} / ,, \\
+ ${c4} / | | -\\
+ ${c4} /_-'' ''-_\\
+ EOF
+ ;;
+
+ ([Aa]rco*)
+ read_ascii 4 <<-EOF
+ ${c4} /\\
+ ${c4} / \\
+ ${c4} / /\\ \\
+ ${c4} / / \\ \\
+ ${c4} / / \\ \\
+ ${c4} / / _____\\ \\
+ ${c4}/_/ \`----.\\_\\
+ EOF
+ ;;
+
+ ([Aa]rtix*)
+ read_ascii 6 <<-EOF
+ ${c4} /\\
+ ${c4} / \\
+ ${c4} /\`'.,\\
+ ${c4} / ',
+ ${c4} / ,\`\\
+ ${c4} / ,.'\`. \\
+ ${c4}/.,'\` \`'.\\
+ EOF
+ ;;
+
+ ([Bb]edrock*)
+ read_ascii 4 <<-EOF
+ ${c7}__
+ ${c7}\\ \\___
+ ${c7} \\ _ \\
+ ${c7} \\___/
+ EOF
+ ;;
+
+ ([Bb]uildroot*)
+ read_ascii 3 <<-EOF
+ ${c3} ___
+ ${c3} / \` \\
+ ${c3}| : :|
+ ${c3}-. _:__.-
+ ${c3} \` ---- \`
+ EOF
+ ;;
+
+ ([Cc]el[Oo][Ss]*)
+ read_ascii 5 0 <<-EOF
+ ${c5} .////\\\\\//\\.
+ ${c5} //_ \\\\
+ ${c5} /_ ${c7}##############
+ ${c5} // *\\
+ ${c7}############### ${c5}|#
+ ${c5} \/ */
+ ${c5} \* ${c7}##############
+ ${c5} */, .//
+ ${c5} '_///\\\\\//_'
+ EOF
+ ;;
+
+ ([Cc]ent[Oo][Ss]*)
+ read_ascii 5 <<-EOF
+ ${c2} ____${c3}^${c5}____
+ ${c2} |\\ ${c3}|${c5} /|
+ ${c2} | \\ ${c3}|${c5} / |
+ ${c5}<---- ${c4}---->
+ ${c4} | / ${c2}|${c3} \\ |
+ ${c4} |/__${c2}|${c3}__\\|
+ ${c2} v
+ EOF
+ ;;
+
+ ([Cc]rystal*[Ll]inux)
+ read_ascii 5 5 <<-EOF
+ ${c5} -//.
+ ${c5} -//.
+ ${c5} -//. .
+ ${c5} -//. '//-
+ ${c5} /+: :+/
+ ${c5} .//' .//.
+ ${c5} . .//.
+ ${c5} .//.
+ ${c5} .//.
+ EOF
+ ;;
+
+ ([Dd]ahlia*)
+ read_ascii 1 <<-EOF
+ ${c1} _
+ ${c1} ___/ \\___
+ ${c1} | _-_ |
+ ${c1} | / \ |
+ ${c1}/ | | \\
+ ${c1}\\ | | /
+ ${c1} | \ _ _ / |
+ ${c1} |___ - ___|
+ ${c1} \\_/
+ EOF
+ ;;
+
+ ([Dd]ebian*)
+ read_ascii 1 <<-EOF
+ ${c1} _____
+ ${c1} / __ \\
+ ${c1}| / |
+ ${c1}| \\___-
+ ${c1}-_
+ ${c1} --_
+ EOF
+ ;;
+
+ ([Dd]evuan*)
+ read_ascii 6 <<-EOF
+ ${c4} ..:::.
+ ${c4} ..-==-
+ ${c4} .+#:
+ ${c4} =@@
+ ${c4} :+%@#:
+ ${c4}.:=+#@@%*:
+ ${c4}#@@@#=:
+ EOF
+ ;;
+
+ ([Dd]ragon[Ff]ly*)
+ read_ascii 1 <<-EOF
+ ,${c1}_${c7},
+ ('-_${c1}|${c7}_-')
+ >--${c1}|${c7}--<
+ (_-'${c1}|${c7}'-_)
+ ${c1}|
+ ${c1}|
+ ${c1}|
+ EOF
+ ;;
+
+ ([Ee]lementary*)
+ read_ascii <<-EOF
+ ${c7} _______
+ ${c7} / ____ \\
+ ${c7}/ | / /\\
+ ${c7}|__\\ / / |
+ ${c7}\\ /__/ /
+ ${c7}\\_______/
+ EOF
+ ;;
+
+ ([Ee]ndeavour*)
+ read_ascii 4 <<-EOF
+ ${c1}/${c4}\\
+ ${c1}/${c4}/ \\${c6}\\
+ ${c1}/${c4}/ \\ ${c6}\\
+ ${c1}/ ${c4}/ _) ${c6})
+ ${c1}/_${c4}/___-- ${c6}__-
+ ${c6}/____--
+ EOF
+ ;;
+
+ ([Ff]edora*)
+ read_ascii 4 <<-EOF
+ ${c4},'''''.
+ ${c4}| ,. |
+ ${c4}| | '_'
+ ${c4} ,....| |..
+ ${c4}.' ,_;| ..'
+ ${c4}| | | |
+ ${c4}| ',_,' |
+ ${c4} '. ,'
+ ${c4}'''''
+ EOF
+ ;;
+
+ ([Ff]ree[Bb][Ss][Dd]*)
+ read_ascii 1 <<-EOF
+ ${c1}/\\,-'''''-,/\\
+ ${c1}\\_) (_/
+ ${c1}| |
+ ${c1}| |
+ ${c1}; ;
+ ${c1}'-_____-'
+ EOF
+ ;;
+
+ ([Gg]aruda*)
+ read_ascii 4 <<-EOF
+ ${c3} _______
+ ${c3} __/ \\_
+ ${c3} _/ / \\_
+ ${c7} _/ /_________\\
+ ${c7}_/ |
+ ${c2}\\ ____________
+ ${c2} \\_ __/
+ ${c2} \\__________/
+ EOF
+ ;;
+
+ ([Gg]entoo*)
+ read_ascii 5 <<-EOF
+ ${c5} _-----_
+ ${c5}( \\
+ ${c5}\\ 0 \\
+ ${c7} \\ )
+ ${c7} / _/
+ ${c7}( _-
+ ${c7}\\____-
+ EOF
+ ;;
+
+ ([Gg][Nn][Uu]*)
+ read_ascii 3 <<-EOF
+ ${c2} _-\`\`-, ,-\`\`-_
+ ${c2} .' _-_| |_-_ '.
+ ${c2}./ /_._ _._\\ \\.
+ ${c2}: _/_._\`:'_._\\_ :
+ ${c2}\\:._/ ,\` \\ \\ \\_.:/
+ ${c2} ,-';'.@) \\ @) \\
+ ${c2} ,'/' ..- .\\,-.|
+ ${c2} /'/' \\(( \\\` ./ )
+ ${c2} '/'' \\_,----'
+ ${c2} '/'' ,;/''
+ ${c2} \`\`;'
+ EOF
+ ;;
+
+ ([Gg]uix[Ss][Dd]*|[Gg]uix*)
+ read_ascii 3 <<-EOF
+ ${c3}|.__ __.|
+ ${c3}|__ \\ / __|
+ ${c3}\\ \\ / /
+ ${c3}\\ \\ / /
+ ${c3}\\ \\ / /
+ ${c3}\\ \\/ /
+ ${c3}\\__/
+ EOF
+ ;;
+
+ ([Hh]aiku*)
+ read_ascii 3 <<-EOF
+ ${c3} ,^,
+ ${c3} / \\
+ ${c3}*--_ ; ; _--*
+ ${c3}\\ '" "' /
+ ${c3}'. .'
+ ${c3}.-'" "'-.
+ ${c3}'-.__. .__.-'
+ ${c3}|_|
+ EOF
+ ;;
+
+ ([Hh]ydroOS*)
+ read_ascii 4 <<-EOF
+ ${c1}╔╗╔╗──╔╗───╔═╦══╗
+ ${c1}║╚╝╠╦╦╝╠╦╦═╣║║══╣
+ ${c1}║╔╗║║║╬║╔╣╬║║╠══║
+ ${c1}╚╝╚╬╗╠═╩╝╚═╩═╩══╝
+ ${c1}───╚═╝
+ EOF
+ ;;
+
+ ([Hh]yperbola*)
+ read_ascii <<-EOF
+ ${c7} |\`__.\`/
+ ${c7} \____/
+ ${c7} .--.
+ ${c7} / \\
+ ${c7} / ___ \\
+ ${c7}/ .\` \`.\\
+ ${c7}/.\` \`.\\
+ EOF
+ ;;
+
+ ([Ii]glunix*)
+ read_ascii <<-EOF
+ ${c0} |
+ ${c0} | |
+ ${c0} |
+ ${c0} | ________
+ ${c0} | /\\ | \\
+ ${c0} / \\ | \\ |
+ ${c0} / \\ \\ |
+ ${c0} / \\________\\
+ ${c0} \\ / /
+ ${c0} \\ / /
+ ${c0} \\ / /
+ ${c0} \\/________/
+ EOF
+ ;;
+
+ ([Ii]nstant[Oo][Ss]*)
+ read_ascii <<-EOF
+ ${c0} ,-''-,
+ ${c0}: .''. :
+ ${c0}: ',,' :
+ ${c0} '-____:__
+ ${c0} : \`.
+ ${c0} \`._.'
+ EOF
+ ;;
+
+ ([Ii][Rr][Ii][Xx]*)
+ read_ascii 1 <<-EOF
+ ${c1} __
+ ${c1} \\ \\ __
+ ${c1} \\ \\ / /
+ ${c1} \\ v /
+ ${c1} / . \\
+ ${c1} /_/ \\ \\
+ ${c1} \\_\\
+ EOF
+ ;;
+
+ ([Kk][Dd][Ee]*[Nn]eon*)
+ read_ascii 6 <<-EOF
+ ${c7} .${c6}__${c7}.${c6}__${c7}.
+ ${c6} / _${c7}.${c6}_ \\
+ ${c6} / / \\ \\
+ ${c7} . ${c6}| ${c7}O${c6} | ${c7}.
+ ${c6} \\ \\_${c7}.${c6}_/ /
+ ${c6} \\${c7}.${c6}__${c7}.${c6}__${c7}.${c6}/
+ EOF
+ ;;
+
+ ([Ll]inux*[Ll]ite*|[Ll]ite*)
+ read_ascii 3 <<-EOF
+ ${c3} /\\
+ ${c3} / \\
+ ${c3} / ${c7}/ ${c3}/
+ ${c3}> ${c7}/ ${c3}/
+ ${c3}\\ ${c7}\\ ${c3}\\
+ ${c3}\\_${c7}\\${c3}_\\
+ ${c7} \\
+ EOF
+ ;;
+
+ ([Ll]inux*[Mm]int*|[Mm]int)
+ read_ascii 2 <<-EOF
+ ${c2} ___________
+ ${c2}|_ \\
+ ${c2}| ${c7}| _____ ${c2}|
+ ${c2}| ${c7}| | | | ${c2}|
+ ${c2}| ${c7}| | | | ${c2}|
+ ${c2}| ${c7}\\__${c7}___/ ${c2}|
+ ${c2}\\_________/
+ EOF
+ ;;
+
+
+ ([Ll]inux*)
+ read_ascii 4 <<-EOF
+ ${c4} ___
+ ${c4}(${c7}.. ${c4}|
+ ${c4}(${c5}<> ${c4}|
+ ${c4}/ ${c7}__ ${c4}\\
+ ${c4}( ${c7}/ \\ ${c4}/|
+ ${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4})
+ ${c5}\/${c4}-____${c5}\/
+ EOF
+ ;;
+
+ ([Mm]ac[Oo][Ss]*|[Dd]arwin*)
+ read_ascii 1 <<-EOF
+ ${c2} .:'
+ ${c2} _ :'_
+ ${c3} .'\`_\`-'_\`\`.
+ ${c1}:________.-'
+ ${c1}:_______:
+ ${c4} :_______\`-;
+ ${c5} \`._.-._.'
+ EOF
+ ;;
+
+ ([Mm]ageia*)
+ read_ascii 2 <<-EOF
+ ${c6} *
+ ${c6} *
+ ${c6} **
+ ${c7} /\\__/\\
+ ${c7}/ \\
+ ${c7}\\ /
+ ${c7} \\____/
+ EOF
+ ;;
+
+ ([Mm]anjaro*)
+ read_ascii 2 <<-EOF
+ ${c2}||||||||| ||||
+ ${c2}||||||||| ||||
+ ${c2}|||| ||||
+ ${c2}|||| |||| ||||
+ ${c2}|||| |||| ||||
+ ${c2}|||| |||| ||||
+ ${c2}|||| |||| ||||
+ EOF
+ ;;
+
+ ([Mm]inix*)
+ read_ascii 4 <<-EOF
+ ${c4} ,, ,,
+ ${c4};${c7},${c4} ', ,' ${c7},${c4};
+ ${c4}; ${c7}',${c4} ',,' ${c7},'${c4} ;
+ ${c4}; ${c7}',${c4} ${c7},'${c4} ;
+ ${c4}; ${c7};, '' ,;${c4} ;
+ ${c4}; ${c7};${c4};${c7}',,'${c4};${c7};${c4} ;
+ ${c4}', ${c7};${c4};; ;;${c7};${c4} ,'
+ ${c4} '${c7};${c4}' '${c7};${c4}'
+ EOF
+ ;;
+
+ ([Mm][Xx]*)
+ read_ascii <<-EOF
+ ${c7} \\\\ /
+ ${c7} \\\\/
+ ${c7} \\\\
+ ${c7} /\\/ \\\\
+ ${c7} / \\ /\\
+ ${c7} / \\/ \\
+ ${c7}/__________\\
+ EOF
+ ;;
+
+ ([Nn]et[Bb][Ss][Dd]*)
+ read_ascii 3 <<-EOF
+ ${c7}\\\\${c3}\`-______,----__
+ ${c7} \\\\ ${c3}__,---\`_
+ ${c7} \\\\ ${c3}\`.____
+ ${c7} \\\\${c3}-______,----\`-
+ ${c7} \\\\
+ ${c7} \\\\
+ ${c7} \\\\
+ EOF
+ ;;
+
+ ([Nn]ix[Oo][Ss]*)
+ read_ascii 4 <<-EOF
+ ${c4} \\\\ \\\\ //
+ ${c4} ==\\\\__\\\\/ //
+ ${c4} // \\\\//
+ ${c4}==// //==
+ ${c4} //\\\\___//
+ ${c4}// /\\\\ \\\\==
+ ${c4} // \\\\ \\\\
+ EOF
+ ;;
+
+ ([Oo]pen[Bb][Ss][Dd]*)
+ read_ascii 3 <<-EOF
+ ${c3} _____
+ ${c3} \\- -/
+ ${c3} \\_/ \\
+ ${c3} | ${c7}O O${c3} |
+ ${c3} |_ < ) 3 )
+ ${c3} / \\ /
+ ${c3} /-_____-\\
+ EOF
+ ;;
+
+ ([Oo]pen[Ss][Uu][Ss][Ee]*[Tt]umbleweed*)
+ read_ascii 2 <<-EOF
+ ${c2} _____ ______
+ ${c2} / ____\\ / ____ \\
+ ${c2}/ / \`/ / \\ \\
+ ${c2}\\ \\____/ /,____/ /
+ ${c2} \\______/ \\_____/
+ EOF
+ ;;
+
+ ([Oo]pen[Ss][Uu][Ss][Ee]*|[Oo]pen*SUSE*|SUSE*|suse*)
+ read_ascii 2 <<-EOF
+ ${c2} _______
+ ${c2}__| __ \\
+ ${c2} / .\\ \\
+ ${c2} \\__/ |
+ ${c2} _______|
+ ${c2} \\_______
+ ${c2}__________/
+ EOF
+ ;;
+
+ ([Oo]pen[Ww]rt*)
+ read_ascii 1 <<-EOF
+ ${c1} _______
+ ${c1}| |.-----.-----.-----.
+ ${c1}| - || _ | -__| |
+ ${c1}|_______|| __|_____|__|__|
+ ${c1} ________|__| __
+ ${c1}| | | |.----.| |_
+ ${c1}| | | || _|| _|
+ ${c1}|________||__| |____|
+ EOF
+ ;;
+
+ ([Pp]arabola*)
+ read_ascii 5 <<-EOF
+ ${c5} __ __ __ _
+ ${c5}.\`_//_//_/ / \`.
+ ${c5} / .\`
+ ${c5} / .\`
+ ${c5} /.\`
+ ${c5} /\`
+ EOF
+ ;;
+
+ ([Pp]op!_[Oo][Ss]*)
+ read_ascii 6 <<-EOF
+ ${c6}______
+ ${c6}\\ _ \\ __
+ ${c6}\\ \\ \\ \\ / /
+ ${c6}\\ \\_\\ \\ / /
+ ${c6}\\ ___\\ /_/
+ ${c6} \\ \\ _
+ ${c6} __\\_\\__(_)_
+ ${c6}(___________)
+ EOF
+ ;;
+
+ ([Pp]ure[Oo][Ss]*)
+ read_ascii <<-EOF
+ ${c7} _____________
+ ${c7}| _________ |
+ ${c7}| | | |
+ ${c7}| | | |
+ ${c7}| |_________| |
+ ${c7}|_____________|
+ EOF
+ ;;
+
+ ([Rr]aspbian*)
+ read_ascii 1 <<-EOF
+ ${c2} __ __
+ ${c2} (_\\)(/_)
+ ${c1} (_(__)_)
+ ${c1}(_(_)(_)_)
+ ${c1} (_(__)_)
+ ${c1} (__)
+ EOF
+ ;;
+
+ ([Ss]erenity[Oo][Ss]*)
+ read_ascii 4 <<-EOF
+ ${c7} _____
+ ${c1} ,-${c7} -,
+ ${c1} ;${c7} ( ;
+ ${c1}| ${c7}. \_${c1}.,${c7} |
+ ${c1}| ${c7}o _${c1} ',${c7} |
+ ${c1} ; ${c7}(_)${c1} )${c7} ;
+ ${c1} '-_____-${c7}'
+ EOF
+ ;;
+
+ ([Ss]lackware*)
+ read_ascii 4 <<-EOF
+ ${c4} ________
+ ${c4} / ______|
+ ${c4} | |______
+ ${c4} \\______ \\
+ ${c4} ______| |
+ ${c4}| |________/
+ ${c4}|____________
+ EOF
+ ;;
+
+ ([Ss]olus*)
+ read_ascii 4 <<-EOF
+ ${c6}
+ ${c6} /|
+ ${c6} / |\\
+ ${c6} / | \\ _
+ ${c6} /___|__\\_\\
+ ${c6} \\ /
+ ${c6} \`-------´
+ EOF
+ ;;
+
+ ([Ss]un[Oo][Ss]|[Ss]olaris*)
+ read_ascii 3 <<-EOF
+ ${c3} . .; .
+ ${c3} . :; :: ;: .
+ ${c3} .;. .. .. .;.
+ ${c3}.. .. .. ..
+ ${c3} .;, ,;.
+ EOF
+ ;;
+
+ ([Uu]buntu*)
+ read_ascii 3 <<-EOF
+ ${c3} _
+ ${c3} ---(_)
+ ${c3} _/ --- \\
+ ${c3}(_) | |
+ ${c3} \\ --- _/
+ ${c3} ---(_)
+ EOF
+ ;;
+
+ ([Vv]oid*)
+ read_ascii 2 <<-EOF
+ ${c2} _______
+ ${c2} _ \\______ -
+ ${c2}| \\ ___ \\ |
+ ${c2}| | / \ | |
+ ${c2}| | \___/ | |
+ ${c2}| \\______ \\_|
+ ${c2} -_______\\
+ EOF
+ ;;
+
+ ([Xx]eonix*)
+ read_ascii 2 <<-EOF
+ ${c2} ___ ___
+ ${c2}___ \ \/ / ___
+ ${c2}\ \ \ / / /
+ ${c2} \ \/ \/ /
+ ${c2} \ /\ /
+ ${c2} \__/ \__/
+ EOF
+ ;;
+
+ (*)
+ # On no match of a distribution ascii art, this function calls
+ # itself again, this time to look for a more generic OS related
+ # ascii art (KISS Linux -> Linux).
+ [ "$1" ] || {
+ get_ascii "$os"
+ return
+ }
+
+ printf 'error: %s is not currently supported.\n' "$os" >&6
+ printf 'error: Open an issue for support to be added.\n' >&6
+ exit 1
+ ;;
+ esac
+
+ # Store the "width" (longest line) and "height" (number of lines)
+ # of the ascii art for positioning. This script prints to the screen
+ # *almost* like a TUI does. It uses escape sequences to allow dynamic
+ # printing of the information through user configuration.
+ #
+ # Iterate over each line of the ascii art to retrieve the above
+ # information. The 'sed' is used to strip '\033[3Xm' color codes from
+ # the ascii art so they don't affect the width variable.
+ while read -r line; do
+ ascii_height=$((${ascii_height:-0} + 1))
+
+ # This was a ternary operation but they aren't supported in
+ # Minix's shell.
+ [ "${#line}" -gt "${ascii_width:-0}" ] &&
+ ascii_width=${#line}
+
+ # Using '<<-EOF' is the only way to loop over a command's
+ # output without the use of a pipe ('|').
+ # This ensures that any variables defined in the while loop
+ # are still accessible in the script.
+ done <<-EOF
+ $(printf %s "$ascii" | sed 's/\[3.m//g')
+ EOF
+
+ # Add a gap between the ascii art and the information.
+ ascii_width=$((ascii_width + 4))
+
+ # Print the ascii art and position the cursor back where we
+ # started prior to printing it.
+ {
+ esc_p SGR 1
+ printf '%s' "$ascii"
+ esc_p SGR 0
+ esc_p CUU "$ascii_height"
+ } >&6
+}
+
+main() {
+ case $* in
+ -v)
+ printf '%s 0.7.0\n' "${0##*/}"
+ return 0
+ ;;
+
+ -d)
+ # Below exec is not run, stderr is shown.
+ ;;
+
+ '')
+ exec 2>/dev/null
+ ;;
+
+ *)
+ cat <<EOF
+${0##*/} show system information
+${0##*/} -d show stderr (debug mode)
+${0##*/} -v show version information
+EOF
+ return 0
+ ;;
+ esac
+
+ # Hide 'stdout' and selectively print to it using '>&6'.
+ # This gives full control over what it displayed on the screen.
+ exec 6>&1 >/dev/null
+
+ # Store raw escape sequence character for later reuse.
+ esc_c=$(printf '\033')
+
+ # Allow the user to execute their own script and modify or
+ # extend pfetch's behavior.
+ # shellcheck source=/dev/null
+ ! [ -f "$PF_SOURCE" ] || . "$PF_SOURCE"
+
+ # Ensure that the 'TMPDIR' is writable as heredocs use it and
+ # fail without the write permission. This was found to be the
+ # case on Android where the temporary directory requires root.
+ [ -w "${TMPDIR:-/tmp}" ] || export TMPDIR=~
+
+ # Generic color list.
+ # Disable warning about unused variables.
+ # shellcheck disable=2034
+ for _c in c1 c2 c3 c4 c5 c6 c7 c8; do
+ esc SGR "3${_c#?}" 0
+ export "$_c=$e"
+ done
+
+ # Disable line wrapping and catch the EXIT signal to enable it again
+ # on exit. Ideally you'd somehow query the current value and retain
+ # it but I'm yet to see this irk anyone.
+ esc_p DECAWM l >&6
+ trap 'esc_p DECAWM h >&6' EXIT
+
+ # Store the output of 'uname' to avoid calling it multiple times
+ # throughout the script. 'read <<EOF' is the simplest way of reading
+ # a command into a list of variables.
+ read -r os kernel arch <<-EOF
+ $(uname -srm)
+ EOF
+
+ # Always run 'get_os' for the purposes of detecting which ascii
+ # art to display.
+ get_os
+
+ # Allow the user to specify the order and inclusion of information
+ # functions through the 'PF_INFO' environment variable.
+ # shellcheck disable=2086
+ {
+ # Disable globbing and set the positional parameters to the
+ # contents of 'PF_INFO'.
+ set -f
+ set +f -- ${PF_INFO-ascii title os host kernel uptime pkgs memory}
+
+ # Iterate over the info functions to determine the lengths of the
+ # "info names" for output alignment. The option names and subtitles
+ # match 1:1 so this is thankfully simple.
+ for info do
+ command -v "get_$info" >/dev/null || continue
+
+ # This was a ternary operation but they aren't supported in
+ # Minix's shell.
+ [ "${#info}" -gt "${info_length:-0}" ] &&
+ info_length=${#info}
+ done
+
+ # Add an additional space of length to act as a gap.
+ info_length=$((info_length + 1))
+
+ # Iterate over the above list and run any existing "get_" functions.
+ for info do
+ "get_$info"
+ done
+ }
+
+ # Position the cursor below both the ascii art and information lines
+ # according to the height of both. If the information exceeds the ascii
+ # art in height, don't touch the cursor (0/unset), else move it down
+ # N lines.
+ #
+ # This was a ternary operation but they aren't supported in Minix's shell.
+ [ "${info_height:-0}" -lt "${ascii_height:-0}" ] &&
+ cursor_pos=$((ascii_height - info_height))
+
+ # Print '$cursor_pos' amount of newlines to correctly position the
+ # cursor. This used to be a 'printf $(seq X X)' however 'seq' is only
+ # typically available (by default) on GNU based systems!
+ while [ "${i:=0}" -le "${cursor_pos:-0}" ]; do
+ printf '\n'
+ i=$((i + 1))
+ done >&6
+}
+
+main "$@"
diff --git a/mbsyncrc b/mbsyncrc
new file mode 100644
index 0000000..011cf4a
--- /dev/null
+++ b/mbsyncrc
@@ -0,0 +1,36 @@
+CopyArrivalDate yes # Don't mess up message timestamps when moving them between folders.
+Create Near # Automatically create new folders in the local copy.
+Remove Near # Automatically remove deleted folders from the local copy.
+Expunge Near # Expunge deleted messages from the local copy.
+
+# First section: remote IMAP account
+IMAPAccount Home
+Host imap.fastmail.com
+Port 993
+User marcc@fastmail.fr
+# For simplicity, this is how to read the password from another file.
+# For better security you should use GPG https://gnupg.org/
+PassCmd "pass fastmail/marcc"
+SSLType IMAPS
+SSLVersions TLSv1.2
+
+IMAPStore Home-remote
+Account Home
+
+# This section describes the local storage
+MaildirStore Home-local
+Path ~/mail-home/
+Inbox ~/mail-home/INBOX
+SubFolders Verbatim
+
+# This section a "channel", a connection between remote and local
+Channel Home
+Far :Home-remote:
+Near :Home-local:
+Patterns * !.nnmaildir !*/.nnmaildir
+Expunge None
+CopyArrivalDate no
+Sync All
+Create Near
+SyncState *
+
diff --git a/profile b/profile
new file mode 100644
index 0000000..88af8f3
--- /dev/null
+++ b/profile
@@ -0,0 +1,24 @@
+# $OpenBSD: dot.profile,v 1.8 2022/08/10 07:40:37 tb Exp $
+#
+# sh/ksh initialization
+
+PATH=$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin
+export PATH HOME TERM
+
+export EDITOR="vim"
+export GDK_DPI_SCALE="1.5"
+export GDK_SCALE="1.5"
+export XCURSOR_SIZE="32"
+export PF_INFO="ascii title os editor shell wm"
+export PF_ALIGN="5"
+export PF_COLOR="0"
+export MOZ_ACCELERATED=1
+
+test -r /home/mccd/.opam/opam-init/init.sh && . /home/mccd/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true
+export PATH=${PATH}:~/.local/bin
+export XDG_DATA_DIRS=${XDG_DATA_DIRS}:~/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share
+export ENV=$HOME/.kshrc
+export HISTSIZE=65535
+export HISTFILE=$HOME/.history
+export STITCH_DIRECTORY=/home/mccd/notes
+export STITCH_GREP_CMD=ugrep
diff --git a/tigrc b/tigrc
new file mode 100644
index 0000000..b371702
--- /dev/null
+++ b/tigrc
@@ -0,0 +1,103 @@
+set git-colors = yes
+set mouse = yes
+set main-view-date = custom
+set main-view-date-format = "%y/%m/%d %H:%M"
+set main-view = date author:abbreviated commit-title:graph:v2
+set tree-view = file-name
+
+color cursor 255 black bold
+color default black default
+color "Reported-by:" black default
+color author black default bold
+color title-focus black default underline
+color title-blur black default underline
+color stat-none black default
+color stat-staged black default
+color stat-unstaged black default
+color status.header black default bold
+
+color file black default
+color directory black default
+color graph-commit black default
+
+color palette-0 black default
+color palette-1 blue default
+color palette-2 black default
+color palette-3 blue default
+color palette-4 black default
+color palette-5 blue default
+color palette-6 black default
+color palette-7 blue default
+color palette-8 black default
+color palette-9 blue default
+color palette-10 black default
+color palette-11 blue default
+color palette-12 black default
+color palette-13 blue default
+
+color main-commit black default
+color main-head black default bold
+color main-annotated black default
+color main-remote black default
+color main-tracked black default
+color main-tag black default
+color main-local-tag black default
+color main-ref black default
+
+color stat-none black default
+color stat-staged red default
+color stat-unstaged blue default
+color stat-untracked black default
+color status.header black default bold
+
+color help.header black default bold
+color help-group black default
+color help-action black default
+
+color pp-refs black default
+color pp-reflog black default
+color pp-reflogmsg black default
+color pp-merge black default
+
+color tree black default
+color committer black default
+color author black default bold
+color commit black default
+color parent black default
+color search-result green black standout
+color date 244 default
+
+color diff-header black default
+color diff-index black default
+color diff-chunk black default
+color diff-stat black default bold
+color diff-add black 194
+color diff-add2 black 194
+color diff-del black 224
+color diff-del2 black 218
+
+bind diff D !git difftool --tool=vimdiff --trust-exit-code --no-prompt %(commit)^! -- %(file)
+bind main C ?git cherry-pick %(commit)
+bind diff C ?git cherry-pick %(commit)
+bind pager C !git commit
+bind stage C !git commit
+bind main @Q !git reset %(commit)
+bind main @R !git rebase -i %(commit)
+bind generic R refresh
+bind status PU !git push
+bind main PU !git push
+bind status PFU ?git push --force-with-lease
+bind main PFU ?git push --force-with-lease
+bind status @@ ?sh -c 'git rev-parse --show-toplevel | xargs -I {} rm "{}/%(file)"'
+bind main MM !git merge --ff-only %(commit)
+bind generic T >git notes append %(commit)
+bind generic d !sh -c "git show %(commit)"
+bind tree @F >sh -c "echo 'New File Name (Blank cancels):'; read line; touch %(directory)/$line"
+bind tree @D >sh -c "echo 'New Folder Name (Blank cancels):'; read line; mkdir %(directory)/$line"
+
+bind blob E >sh -c "$EDITOR %(file)"
+
+bind tree - parent
+set pager-view = line-number:yes,interval=10 text
+
+
diff --git a/tmux.conf b/tmux.conf
new file mode 100644
index 0000000..bd80dbd
--- /dev/null
+++ b/tmux.conf
@@ -0,0 +1,3 @@
+set -g mouse on
+set -g status off
+set -s escape-time 0
diff --git a/vim/downloads/opt/snippets/snippets.vim b/vim/downloads/opt/snippets/snippets.vim
new file mode 100644
index 0000000..de15141
--- /dev/null
+++ b/vim/downloads/opt/snippets/snippets.vim
@@ -0,0 +1,6 @@
+autocmd FileType go
+ \ :iabbrev <buffer> err@ if err != nil {}<Left><Enter><Left>
+
+autocmd FileType go
+ \ :iabbrev <buffer> fat@ log.Fatal(" %v", err)<Esc>F%i<Left>
+
diff --git a/vim/pack/downloads/opt/lsp/.gitignore b/vim/pack/downloads/opt/lsp/.gitignore
new file mode 100644
index 0000000..b2e4d35
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/.gitignore
@@ -0,0 +1,8 @@
+tags
+*.swp
+
+test/X*.c
+test/X*.cpp
+# test/Xtest.c
+# test/Xtest.cpp
+test/results.txt
diff --git a/vim/pack/downloads/opt/lsp/LICENSE b/vim/pack/downloads/opt/lsp/LICENSE
new file mode 100644
index 0000000..9e55f93
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2023 Yegappan Lakshmanan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vim/pack/downloads/opt/lsp/README.md b/vim/pack/downloads/opt/lsp/README.md
new file mode 100644
index 0000000..a2abb6d
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/README.md
@@ -0,0 +1,234 @@
+[![unit-tests](https://github.com/yegappan/lsp/workflows/unit-tests/badge.svg?branch=main)](https://github.com/yegappan/lsp/actions/workflows/unitests.yml?query=branch%3Amain)
+
+Language Server Protocol (LSP) plugin for Vim. You need Vim version 9.0 or above to use this plugin. This plugin is written using only the Vim9 script.
+
+## Installation
+
+You can install this plugin directly from github using the following steps:
+
+```bash
+$ mkdir -p $HOME/.vim/pack/downloads/opt
+$ cd $HOME/.vim/pack/downloads/opt
+$ git clone https://github.com/yegappan/lsp
+$ vim -u NONE -c "helptags $HOME/.vim/pack/downloads/opt/lsp/doc" -c q
+```
+
+After installing the plugin using the above steps, add the following line to
+your $HOME/.vimrc file:
+
+```viml
+packadd lsp
+```
+
+You can also install and manage this plugin using any one of the Vim plugin managers (dein.vim, pathogen, vam, vim-plug, volt, Vundle, etc.).
+
+You will also need to download and install one or more language servers corresponding to the programming languages that you are using. Refer to the https://langserver.org/ page for the list of available language servers. This plugin doesn't install the language servers.
+
+## Features
+
+The following language server protocol (LSP) features are supported:
+
+* Code completion
+* Jump to definition, declaration, implementation, type definition
+* Peek definition, declaration, implementation, type definition and references
+* Display warning and error diagnostics
+* Find all symbol references
+* Document and Workspace symbol search
+* Display code outline
+* Rename symbol
+* Display type and documentation on hover
+* Signature help
+* Code action
+* Display Call hierarchy
+* Display Type hierarchy
+* Highlight current symbol references
+* Formatting code
+* Folding code
+* Inlay hints
+* Visually select symbol block/region
+* Semantic Highlight
+
+## Configuration
+
+To use the plugin features with a particular file type(s), you need to first register a LSP server for that file type(s).
+
+The LSP servers are registered using the LspAddServer() function. This function accepts a list of LSP servers.
+
+To register a LSP server, add the following lines to your .vimrc file (use only the LSP servers that you need from the below list). If you used [vim-plug](https://github.com/junegunn/vim-plug) to install the LSP plugin, the steps are described later in this section.
+```viml
+
+" Clangd language server
+call LspAddServer([#{
+ \ name: 'clangd',
+ \ filetype: ['c', 'cpp'],
+ \ path: '/usr/local/bin/clangd',
+ \ args: ['--background-index']
+ \ }])
+
+" Javascript/Typescript language server
+call LspAddServer([#{
+ \ name: 'typescriptlang',
+ \ filetype: ['javascript', 'typescript'],
+ \ path: '/usr/local/bin/typescript-language-server',
+ \ args: ['--stdio'],
+ \ }])
+
+" Go language server
+call LspAddServer([#{
+ \ name: 'golang',
+ \ filetype: ['go', 'gomod'],
+ \ path: '/usr/local/bin/gopls',
+ \ args: ['serve'],
+ \ syncInit: v:true
+ \ }])
+
+" Rust language server
+call LspAddServer([#{
+ \ name: 'rustlang',
+ \ filetype: ['rust'],
+ \ path: '/usr/local/bin/rust-analyzer',
+ \ args: [],
+ \ syncInit: v:true
+ \ }])
+```
+
+The above lines register the language servers for C/C++, Javascript/Typescript, Go and Rust file types. Refer to the [Wiki](https://github.com/yegappan/lsp/wiki) page for various language server specific configuration.
+
+To register a LSP server, the following information is needed:
+
+Field|Description
+-----|-----------
+filetype|One or more file types supported by the LSP server. This can be a String or a List. To specify multiple multiple file types, use a List.
+path|complete path to the LSP server executable (without any arguments).
+args|a list of command-line arguments passed to the LSP server. Each argument is a separate List item.
+initializationOptions|User provided initialization options. May be of any type. For example the *intelephense* PHP language server accept several options here with the License Key among others.
+customNotificationHandlers|A dictionary of notifications and functions that can be specified to add support for custom language server notifications.
+customRequestHandlers|A dictionary of request handlers and functions that can be specified to add support for custom language server requests replies.
+features|A dictionary of booleans that can be specified to toggle what things a given LSP is providing (folding, goto definition, etc) This is useful when running multiple servers in one buffer.
+
+The LspAddServer() function accepts a list of LSP servers with the above information.
+
+Some of the LSP plugin features can be enabled or disabled by using the LspOptionsSet() function, detailed in `:help lsp-options`.
+Here is an example of configuration with default values:
+```viml
+call LspOptionsSet(#{
+ \ aleSupport: v:false,
+ \ autoComplete: v:true,
+ \ autoHighlight: v:false,
+ \ autoHighlightDiags: v:true,
+ \ autoPopulateDiags: v:false,
+ \ completionMatcher: 'case',
+ \ completionMatcherValue: 1,
+ \ diagSignErrorText: 'E>',
+ \ diagSignHintText: 'H>',
+ \ diagSignInfoText: 'I>',
+ \ diagSignWarningText: 'W>',
+ \ echoSignature: v:false,
+ \ hideDisabledCodeActions: v:false,
+ \ highlightDiagInline: v:true,
+ \ hoverInPreview: v:false,
+ \ ignoreMissingServer: v:false,
+ \ keepFocusInDiags: v:true,
+ \ keepFocusInReferences: v:true,
+ \ completionTextEdit: v:true,
+ \ diagVirtualTextAlign: 'above',
+ \ diagVirtualTextWrap: 'default',
+ \ noNewlineInCompletion: v:false,
+ \ omniComplete: v:null,
+ \ outlineOnRight: v:false,
+ \ outlineWinSize: 20,
+ \ semanticHighlight: v:true,
+ \ showDiagInBalloon: v:true,
+ \ showDiagInPopup: v:true,
+ \ showDiagOnStatusLine: v:false,
+ \ showDiagWithSign: v:true,
+ \ showDiagWithVirtualText: v:false,
+ \ showInlayHints: v:false,
+ \ showSignature: v:true,
+ \ snippetSupport: v:false,
+ \ ultisnipsSupport: v:false,
+ \ useBufferCompletion: v:false,
+ \ usePopupInCodeAction: v:false,
+ \ useQuickfixForLocations: v:false,
+ \ vsnipSupport: v:false,
+ \ bufferCompletionTimeout: 100,
+ \ customCompletionKinds: v:false,
+ \ completionKinds: {},
+ \ filterCompletionDuplicates: v:false,
+ \ })
+```
+
+If you used [vim-plug](https://github.com/junegunn/vim-plug) to install the LSP plugin, then you need to use the LspSetup User autocmd to initialize the LSP server and to set the LSP server options. For example:
+```viml
+let lspOpts = #{autoHighlightDiags: v:true}
+autocmd User LspSetup call LspOptionsSet(lspOpts)
+
+let lspServers = [#{
+ \ name: 'clang',
+ \ filetype: ['c', 'cpp'],
+ \ path: '/usr/local/bin/clangd',
+ \ args: ['--background-index']
+ \ }]
+autocmd User LspSetup call LspAddServer(lspServers)
+```
+
+## Supported Commands
+
+The following commands are provided to use the LSP features.
+
+Command|Description
+-------|-----------
+:LspCodeAction|Apply the code action supplied by the language server to the diagnostic in the current line.
+:LspCodeLens|Display a list of code lens commands and apply a selected code lens command to the current file.
+:LspDiag current|Display the diagnostic message for the current line.
+:LspDiag first|Jump to the first diagnostic message for the current buffer.
+:LspDiag here|Jump to the next diagnostic message in the current line.
+:LspDiag highlight disable|Disable diagnostic message highlights.
+:LspDiag highlight enable|Enable diagnostic message highlights.
+:LspDiag next|Jump to the next diagnostic message after the current position.
+:LspDiag nextWrap|Jump to the next diagnostic message after the current position, wrapping to the first message when the last message is reached.
+:LspDiag prev|Jump to the previous diagnostic message before the current position.
+:LspDiag prevWrap|Jump to the previous diagnostic message before the current position, wrapping to the last message when the first message is reached.
+:LspDiag show|Display the diagnostics messages from the language server for the current buffer in a new location list.
+:LspDocumentSymbol|Display the symbols in the current file in a popup menu and jump to the selected symbol.
+:LspFold|Fold the current file.
+:LspFormat|Format a range of lines in the current file using the language server. The **shiftwidth** and **expandtab** values set for the current buffer are used when format is applied. The default range is the entire file.
+:LspGotoDeclaration|Go to the declaration of the keyword under cursor.
+:LspGotoDefinition|Go to the definition of the keyword under cursor.
+:LspGotoImpl|Go to the implementation of the keyword under cursor.
+:LspGotoTypeDef|Go to the type definition of the keyword under cursor.
+:LspHighlight|Highlight all the matches for the keyword under cursor.
+:LspHighlightClear|Clear all the matches highlighted by :LspHighlight.
+:LspHover|Show the documentation for the symbol under the cursor in a popup window.
+:LspIncomingCalls|Display the list of symbols calling the current symbol.
+:LspOutgoingCalls|Display the list of symbols called by the current symbol.
+:LspOutline|Show the list of symbols defined in the current file in a separate window.
+:LspPeekDeclaration|Open the declaration of the symbol under cursor in the preview window.
+:LspPeekDefinition|Open the definition of the symbol under cursor in the preview window.
+:LspPeekImpl|Open the implementation of the symbol under cursor in the preview window.
+:LspPeekReferences|Display the list of references to the keyword under cursor in a location list associated with the preview window.
+:LspPeekTypeDef|Open the type definition of the symbol under cursor in the preview window.
+:LspRename|Rename the current symbol.
+:LspSelectionExpand|Expand the current symbol range visual selection.
+:LspSelectionShrink|Shrink the current symbol range visual selection.
+:LspShowAllServers|Display information about all the registered language servers.
+:LspServer|Display the capabilities or messages or status of the language server for the current buffer or restart the server.
+:LspShowReferences|Display the list of references to the keyword under cursor in a new location list.
+:LspShowSignature|Display the signature of the keyword under cursor.
+:LspSubTypeHierarchy|Display the sub type hierarchy in a popup window.
+:LspSuperTypeHierarchy|Display the super type hierarchy in a popup window.
+:LspSwitchSourceHeader|Switch between a source and a header file.
+:LspSymbolSearch|Perform a workspace wide search for a symbol.
+:LspWorkspaceAddFolder `{folder}`| Add a folder to the workspace.
+:LspWorkspaceListFolders|Show the list of folders in the workspace.
+:LspWorkspaceRemoveFolder `{folder}`|Remove a folder from the workspace.
+
+## Similar Vim LSP Plugins
+
+1. [vim-lsp: Async Language Server Protocol](https://github.com/prabirshrestha/vim-lsp)
+1. [Coc: Conquer of Completion](https://github.com/neoclide/coc.nvim)
+1. [vim-lsc: Vim Language Server Client](https://github.com/natebosch/vim-lsc)
+1. [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim)
+1. [ALE: Asynchronous Lint Engine](https://github.com/dense-analysis/ale)
+1. [Neovim built-in LSP client](https://neovim.io/doc/user/lsp.html)
+2. [Omnisharp LSP client](https://github.com/OmniSharp/omnisharp-vim)
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim
new file mode 100644
index 0000000..5a07654
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim
@@ -0,0 +1,194 @@
+vim9script
+
+# Functions for managing the per-buffer LSP server information
+
+import './util.vim'
+
+# A buffer can have one or more attached language servers. The
+# "bufnrToServers" Dict contains the list of language servers attached to a
+# buffer. The buffer number is the key for the "bufnrToServers" Dict. The
+# value is the List of attached language servers.
+var bufnrToServers: dict<list<dict<any>>> = {}
+
+# Add "lspserver" to "bufnrToServers" map for buffer "bnr".
+export def BufLspServerSet(bnr: number, lspserver: dict<any>)
+ if !bufnrToServers->has_key(bnr)
+ bufnrToServers[bnr] = []
+ endif
+
+ bufnrToServers[bnr]->add(lspserver)
+enddef
+
+# Remove "lspserver" from "bufnrToServers" map for buffer "bnr".
+export def BufLspServerRemove(bnr: number, lspserver: dict<any>)
+ if !bufnrToServers->has_key(bnr)
+ return
+ endif
+
+ var servers: list<dict<any>> = bufnrToServers[bnr]
+ servers = servers->filter((key, srv) => srv.id != lspserver.id)
+
+ if servers->empty()
+ bufnrToServers->remove(bnr)
+ else
+ bufnrToServers[bnr] = servers
+ endif
+enddef
+
+var SupportedCheckFns = {
+ callHierarchy: (lspserver) => lspserver.isCallHierarchyProvider,
+ codeAction: (lspserver) => lspserver.isCodeActionProvider,
+ codeLens: (lspserver) => lspserver.isCodeLensProvider,
+ completion: (lspserver) => lspserver.isCompletionProvider,
+ declaration: (lspserver) => lspserver.isDeclarationProvider,
+ definition: (lspserver) => lspserver.isDefinitionProvider,
+ documentFormatting: (lspserver) => lspserver.isDocumentFormattingProvider,
+ documentHighlight: (lspserver) => lspserver.isDocumentHighlightProvider,
+ documentSymbol: (lspserver) => lspserver.isDocumentSymbolProvider,
+ foldingRange: (lspserver) => lspserver.isFoldingRangeProvider,
+ hover: (lspserver) => lspserver.isHoverProvider,
+ implementation: (lspserver) => lspserver.isImplementationProvider,
+ inlayHint: (lspserver) => lspserver.isInlayHintProvider ||
+ lspserver.isClangdInlayHintsProvider,
+ references: (lspserver) => lspserver.isReferencesProvider,
+ rename: (lspserver) => lspserver.isRenameProvider,
+ selectionRange: (lspserver) => lspserver.isSelectionRangeProvider,
+ semanticTokens: (lspserver) => lspserver.isSemanticTokensProvider,
+ signatureHelp: (lspserver) => lspserver.isSignatureHelpProvider,
+ typeDefinition: (lspserver) => lspserver.isTypeDefinitionProvider,
+ typeHierarchy: (lspserver) => lspserver.isTypeHierarchyProvider,
+ workspaceSymbol: (lspserver) => lspserver.isWorkspaceSymbolProvider
+}
+
+# Returns the LSP server for the buffer "bnr". If "feature" is specified,
+# then returns the LSP server that provides the "feature".
+# Returns an empty dict if the server is not found.
+export def BufLspServerGet(bnr: number, feature: string = null_string): dict<any>
+ if !bufnrToServers->has_key(bnr)
+ return {}
+ endif
+
+ if bufnrToServers[bnr]->empty()
+ return {}
+ endif
+
+ if feature == null_string
+ return bufnrToServers[bnr][0]
+ endif
+
+ if !SupportedCheckFns->has_key(feature)
+ # If this happns it is a programming error, and should be fixed in the
+ # source code
+ :throw $'Error: ''{feature}'' is not a valid feature'
+ endif
+
+ var SupportedCheckFn = SupportedCheckFns[feature]
+
+ var possibleLSPs: list<dict<any>> = []
+
+ for lspserver in bufnrToServers[bnr]
+ if !lspserver.ready || !SupportedCheckFn(lspserver)
+ continue
+ endif
+
+ possibleLSPs->add(lspserver)
+ endfor
+
+ if possibleLSPs->empty()
+ return {}
+ endif
+
+ # LSP server is configured to be a provider for "feature"
+ for lspserver in possibleLSPs
+ var has_feature: bool = lspserver.features->get(feature, false)
+ if has_feature
+ return lspserver
+ endif
+ endfor
+
+ # Return the first LSP server that supports "feature" and doesn't have it
+ # disabled
+ for lspserver in possibleLSPs
+ if lspserver.featureEnabled(feature)
+ return lspserver
+ endif
+ endfor
+
+ return {}
+enddef
+
+# Returns the LSP server for the buffer "bnr" and with ID "id". Returns an empty
+# dict if the server is not found.
+export def BufLspServerGetById(bnr: number, id: number): dict<any>
+ if !bufnrToServers->has_key(bnr)
+ return {}
+ endif
+
+ for lspserver in bufnrToServers[bnr]
+ if lspserver.id == id
+ return lspserver
+ endif
+ endfor
+
+ return {}
+enddef
+
+# Returns the LSP servers for the buffer "bnr". Returns an empty list if the
+# servers are not found.
+export def BufLspServersGet(bnr: number): list<dict<any>>
+ if !bufnrToServers->has_key(bnr)
+ return []
+ endif
+
+ return bufnrToServers[bnr]
+enddef
+
+# Returns the LSP server for the current buffer with the optionally "feature".
+# Returns an empty dict if the server is not found.
+export def CurbufGetServer(feature: string = null_string): dict<any>
+ return BufLspServerGet(bufnr(), feature)
+enddef
+
+# Returns the LSP servers for the current buffer. Returns an empty list if the
+# servers are not found.
+export def CurbufGetServers(): list<dict<any>>
+ return BufLspServersGet(bufnr())
+enddef
+
+export def BufHasLspServer(bnr: number): bool
+ var lspserver = BufLspServerGet(bnr)
+
+ return !lspserver->empty()
+enddef
+
+# Returns the LSP server for the current buffer with the optinally "feature" if
+# it is running and is ready.
+# Returns an empty dict if the server is not found or is not ready.
+export def CurbufGetServerChecked(feature: string = null_string): dict<any>
+ var fname: string = @%
+ if fname->empty() || &filetype->empty()
+ return {}
+ endif
+
+ var lspserver: dict<any> = CurbufGetServer(feature)
+ if lspserver->empty()
+ if feature == null_string
+ util.ErrMsg($'Language server for "{&filetype}" file type is not found')
+ else
+ util.ErrMsg($'Language server for "{&filetype}" file type supporting "{feature}" feature is not found')
+ endif
+ return {}
+ endif
+ if !lspserver.running
+ util.ErrMsg($'Language server for "{&filetype}" file type is not running')
+ return {}
+ endif
+ if !lspserver.ready
+ util.ErrMsg($'Language server for "{&filetype}" file type is not ready')
+ return {}
+ endif
+
+ return lspserver
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim
new file mode 100644
index 0000000..aca6a55
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim
@@ -0,0 +1,194 @@
+vim9script
+
+# Functions for dealing with call hierarchy (incoming/outgoing calls)
+
+import './util.vim'
+import './buffer.vim' as buf
+
+# Jump to the location of the symbol under the cursor in the call hierarchy
+# tree window.
+def CallHierarchyItemJump()
+ var item: dict<any> = w:LspCallHierItemMap[line('.')].item
+ util.JumpToLspLocation(item, '')
+enddef
+
+# Refresh the call hierarchy tree for the symbol at index "idx".
+def CallHierarchyTreeItemRefresh(idx: number)
+ var treeItem: dict<any> = w:LspCallHierItemMap[idx]
+
+ if treeItem.open
+ # Already retrieved the children for this item
+ return
+ endif
+
+ if !treeItem->has_key('children')
+ # First time retrieving the children for the item at index "idx"
+ var lspserver = buf.BufLspServerGet(w:LspBufnr, 'callHierarchy')
+ if lspserver->empty() || !lspserver.running
+ return
+ endif
+
+ var reply: any
+ if w:LspCallHierIncoming
+ reply = lspserver.getIncomingCalls(treeItem.item)
+ else
+ reply = lspserver.getOutgoingCalls(treeItem.item)
+ endif
+
+ treeItem.children = []
+ if !reply->empty()
+ for item in reply
+ treeItem.children->add({item: w:LspCallHierIncoming ? item.from :
+ item.to, open: false})
+ endfor
+ endif
+ endif
+
+ # Clear and redisplay the tree in the window
+ treeItem.open = true
+ var save_cursor = getcurpos()
+ CallHierarchyTreeRefresh()
+ setpos('.', save_cursor)
+enddef
+
+# Open the call hierarchy tree item under the cursor
+def CallHierarchyTreeItemOpen()
+ CallHierarchyTreeItemRefresh(line('.'))
+enddef
+
+# Refresh the entire call hierarchy tree
+def CallHierarchyTreeRefreshCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Display the incoming call hierarchy tree
+def CallHierarchyTreeIncomingCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ w:LspCallHierIncoming = true
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Display the outgoing call hierarchy tree
+def CallHierarchyTreeOutgoingCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ w:LspCallHierIncoming = false
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Close the call hierarchy tree item under the cursor
+def CallHierarchyTreeItemClose()
+ var treeItem: dict<any> = w:LspCallHierItemMap[line('.')]
+ treeItem.open = false
+ var save_cursor = getcurpos()
+ CallHierarchyTreeRefresh()
+ setpos('.', save_cursor)
+enddef
+
+# Recursively add the call hierarchy items to w:LspCallHierItemMap
+def CallHierarchyTreeItemShow(incoming: bool, treeItem: dict<any>, pfx: string)
+ var item = treeItem.item
+ var treePfx: string
+ if treeItem.open && treeItem->has_key('children')
+ treePfx = has('gui_running') ? '▼' : '-'
+ else
+ treePfx = has('gui_running') ? '▶' : '+'
+ endif
+ var fname = util.LspUriToFile(item.uri)
+ var s = $'{pfx}{treePfx} {item.name} ({fname->fnamemodify(":t")} [{fname->fnamemodify(":h")}])'
+ append('$', s)
+ w:LspCallHierItemMap->add(treeItem)
+ if treeItem.open && treeItem->has_key('children')
+ for child in treeItem.children
+ CallHierarchyTreeItemShow(incoming, child, $'{pfx} ')
+ endfor
+ endif
+enddef
+
+def CallHierarchyTreeRefresh()
+ :setlocal modifiable
+ :silent! :%d _
+
+ setline(1, $'# {w:LspCallHierIncoming ? "Incoming calls to" : "Outgoing calls from"} "{w:LspCallHierarchyTree.item.name}"')
+ w:LspCallHierItemMap = [{}, {}]
+ CallHierarchyTreeItemShow(w:LspCallHierIncoming, w:LspCallHierarchyTree, '')
+ :setlocal nomodifiable
+enddef
+
+def CallHierarchyTreeShow(incoming: bool, prepareItem: dict<any>,
+ items: list<dict<any>>)
+ var save_bufnr = bufnr()
+ var wid = bufwinid('LSP-CallHierarchy')
+ if wid != -1
+ wid->win_gotoid()
+ else
+ :new LSP-CallHierarchy
+ :setlocal buftype=nofile
+ :setlocal bufhidden=wipe
+ :setlocal noswapfile
+ :setlocal nonumber nornu
+ :setlocal fdc=0 signcolumn=no
+
+ :nnoremap <buffer> <CR> <ScriptCmd>CallHierarchyItemJump()<CR>
+ :nnoremap <buffer> - <ScriptCmd>CallHierarchyTreeItemOpen()<CR>
+ :nnoremap <buffer> + <ScriptCmd>CallHierarchyTreeItemClose()<CR>
+ :command -buffer LspCallHierarchyRefresh CallHierarchyTreeRefreshCmd()
+ :command -buffer LspCallHierarchyIncoming CallHierarchyTreeIncomingCmd()
+ :command -buffer LspCallHierarchyOutgoing CallHierarchyTreeOutgoingCmd()
+
+ :syntax match Comment '^#.*$'
+ :syntax match Directory '(.*)$'
+ endif
+
+ w:LspBufnr = save_bufnr
+ w:LspCallHierIncoming = incoming
+ w:LspCallHierarchyTree = {}
+ w:LspCallHierarchyTree.item = prepareItem
+ w:LspCallHierarchyTree.open = true
+ w:LspCallHierarchyTree.children = []
+ for item in items
+ w:LspCallHierarchyTree.children->add({item: incoming ? item.from : item.to, open: false})
+ endfor
+
+ CallHierarchyTreeRefresh()
+
+ :setlocal nomodified
+ :setlocal nomodifiable
+enddef
+
+export def IncomingCalls(lspserver: dict<any>)
+ var prepareReply = lspserver.prepareCallHierarchy()
+ if prepareReply->empty()
+ util.WarnMsg('No incoming calls')
+ return
+ endif
+
+ var reply = lspserver.getIncomingCalls(prepareReply)
+ if reply->empty()
+ util.WarnMsg('No incoming calls')
+ return
+ endif
+
+ CallHierarchyTreeShow(true, prepareReply, reply)
+enddef
+
+export def OutgoingCalls(lspserver: dict<any>)
+ var prepareReply = lspserver.prepareCallHierarchy()
+ if prepareReply->empty()
+ util.WarnMsg('No outgoing calls')
+ return
+ endif
+
+ var reply = lspserver.getOutgoingCalls(prepareReply)
+ if reply->empty()
+ util.WarnMsg('No outgoing calls')
+ return
+ endif
+
+ CallHierarchyTreeShow(false, prepareReply, reply)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim
new file mode 100644
index 0000000..46f5973
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim
@@ -0,0 +1,501 @@
+vim9script
+
+# Functions for managing the LSP server and client capabilities
+
+import './options.vim' as opt
+
+# Process the server capabilities
+# interface ServerCapabilities
+export def ProcessServerCaps(lspserver: dict<any>, caps: dict<any>)
+ var serverEncoding = 'utf-16'
+ if lspserver.caps->has_key('positionEncoding')
+ serverEncoding = lspserver.caps.positionEncoding
+ elseif lspserver.caps->has_key('~additionalInitResult_offsetEncoding')
+ serverEncoding = lspserver.caps['~additionalInitResult_offsetEncoding']
+ endif
+
+ if lspserver.forceOffsetEncoding != ''
+ serverEncoding = lspserver.forceOffsetEncoding
+ endif
+
+ # one of 'utf-8', 'utf-16' or 'utf-32'
+ if serverEncoding == 'utf-8'
+ lspserver.posEncoding = 8
+ elseif serverEncoding == 'utf-16'
+ lspserver.posEncoding = 16
+ else
+ lspserver.posEncoding = 32
+ endif
+
+ if has('patch-9.0.1629') && lspserver.posEncoding != 32
+ lspserver.needOffsetEncoding = true
+ else
+ lspserver.needOffsetEncoding = false
+ endif
+
+ # completionProvider
+ if lspserver.caps->has_key('completionProvider')
+ lspserver.isCompletionProvider = true
+ if lspserver.caps.completionProvider->has_key('resolveProvider')
+ lspserver.isCompletionResolveProvider =
+ lspserver.caps.completionProvider.resolveProvider
+ else
+ lspserver.isCompletionResolveProvider = false
+ endif
+ else
+ lspserver.isCompletionProvider = false
+ lspserver.isCompletionResolveProvider = false
+ endif
+
+ # definitionProvider
+ if lspserver.caps->has_key('definitionProvider')
+ if lspserver.caps.definitionProvider->type() == v:t_bool
+ lspserver.isDefinitionProvider = lspserver.caps.definitionProvider
+ else
+ lspserver.isDefinitionProvider = true
+ endif
+ else
+ lspserver.isDefinitionProvider = false
+ endif
+
+ # declarationProvider
+ if lspserver.caps->has_key('declarationProvider')
+ if lspserver.caps.declarationProvider->type() == v:t_bool
+ lspserver.isDeclarationProvider = lspserver.caps.declarationProvider
+ else
+ lspserver.isDeclarationProvider = true
+ endif
+ else
+ lspserver.isDeclarationProvider = false
+ endif
+
+ # typeDefinitionProvider
+ if lspserver.caps->has_key('typeDefinitionProvider')
+ if lspserver.caps.typeDefinitionProvider->type() == v:t_bool
+ lspserver.isTypeDefinitionProvider = lspserver.caps.typeDefinitionProvider
+ else
+ lspserver.isTypeDefinitionProvider = true
+ endif
+ else
+ lspserver.isTypeDefinitionProvider = false
+ endif
+
+ # implementationProvider
+ if lspserver.caps->has_key('implementationProvider')
+ if lspserver.caps.implementationProvider->type() == v:t_bool
+ lspserver.isImplementationProvider = lspserver.caps.implementationProvider
+ else
+ lspserver.isImplementationProvider = true
+ endif
+ else
+ lspserver.isImplementationProvider = false
+ endif
+
+ # signatureHelpProvider
+ if lspserver.caps->has_key('signatureHelpProvider')
+ lspserver.isSignatureHelpProvider = true
+ else
+ lspserver.isSignatureHelpProvider = false
+ endif
+
+ # hoverProvider
+ if lspserver.caps->has_key('hoverProvider')
+ if lspserver.caps.hoverProvider->type() == v:t_bool
+ lspserver.isHoverProvider = lspserver.caps.hoverProvider
+ else
+ lspserver.isHoverProvider = true
+ endif
+ else
+ lspserver.isHoverProvider = false
+ endif
+
+ # referencesProvider
+ if lspserver.caps->has_key('referencesProvider')
+ if lspserver.caps.referencesProvider->type() == v:t_bool
+ lspserver.isReferencesProvider = lspserver.caps.referencesProvider
+ else
+ lspserver.isReferencesProvider = true
+ endif
+ else
+ lspserver.isReferencesProvider = false
+ endif
+
+ # documentHighlightProvider
+ if lspserver.caps->has_key('documentHighlightProvider')
+ if lspserver.caps.documentHighlightProvider->type() == v:t_bool
+ lspserver.isDocumentHighlightProvider =
+ lspserver.caps.documentHighlightProvider
+ else
+ lspserver.isDocumentHighlightProvider = true
+ endif
+ else
+ lspserver.isDocumentHighlightProvider = false
+ endif
+
+ # documentSymbolProvider
+ if lspserver.caps->has_key('documentSymbolProvider')
+ if lspserver.caps.documentSymbolProvider->type() == v:t_bool
+ lspserver.isDocumentSymbolProvider =
+ lspserver.caps.documentSymbolProvider
+ else
+ lspserver.isDocumentSymbolProvider = true
+ endif
+ else
+ lspserver.isDocumentSymbolProvider = false
+ endif
+
+ # documentFormattingProvider
+ if lspserver.caps->has_key('documentFormattingProvider')
+ if lspserver.caps.documentFormattingProvider->type() == v:t_bool
+ lspserver.isDocumentFormattingProvider =
+ lspserver.caps.documentFormattingProvider
+ else
+ lspserver.isDocumentFormattingProvider = true
+ endif
+ else
+ lspserver.isDocumentFormattingProvider = false
+ endif
+
+ # callHierarchyProvider
+ if lspserver.caps->has_key('callHierarchyProvider')
+ if lspserver.caps.callHierarchyProvider->type() == v:t_bool
+ lspserver.isCallHierarchyProvider =
+ lspserver.caps.callHierarchyProvider
+ else
+ lspserver.isCallHierarchyProvider = true
+ endif
+ else
+ lspserver.isCallHierarchyProvider = false
+ endif
+
+ # semanticTokensProvider
+ if lspserver.caps->has_key('semanticTokensProvider')
+ lspserver.isSemanticTokensProvider = true
+ lspserver.semanticTokensLegend =
+ lspserver.caps.semanticTokensProvider.legend
+ lspserver.semanticTokensRange =
+ lspserver.caps.semanticTokensProvider->get('range', false)
+ if lspserver.caps.semanticTokensProvider->has_key('full')
+ if lspserver.caps.semanticTokensProvider.full->type() == v:t_bool
+ lspserver.semanticTokensFull =
+ lspserver.caps.semanticTokensProvider.full
+ lspserver.semanticTokensDelta = false
+ else
+ lspserver.semanticTokensFull = true
+ if lspserver.caps.semanticTokensProvider.full->has_key('delta')
+ lspserver.semanticTokensDelta =
+ lspserver.caps.semanticTokensProvider.full.delta
+ else
+ lspserver.semanticTokensDelta = false
+ endif
+ endif
+ else
+ lspserver.semanticTokensFull = false
+ lspserver.semanticTokensDelta = false
+ endif
+ else
+ lspserver.isSemanticTokensProvider = false
+ endif
+
+ # typeHierarchyProvider
+ if lspserver.caps->has_key('typeHierarchyProvider')
+ lspserver.isTypeHierarchyProvider = true
+ else
+ lspserver.isTypeHierarchyProvider = false
+ endif
+
+ # renameProvider
+ if lspserver.caps->has_key('renameProvider')
+ if lspserver.caps.renameProvider->type() == v:t_bool
+ lspserver.isRenameProvider = lspserver.caps.renameProvider
+ else
+ lspserver.isRenameProvider = true
+ endif
+ else
+ lspserver.isRenameProvider = false
+ endif
+
+ # codeActionProvider
+ if lspserver.caps->has_key('codeActionProvider')
+ if lspserver.caps.codeActionProvider->type() == v:t_bool
+ lspserver.isCodeActionProvider = lspserver.caps.codeActionProvider
+ else
+ lspserver.isCodeActionProvider = true
+ endif
+ else
+ lspserver.isCodeActionProvider = false
+ endif
+
+ # codeLensProvider
+ if lspserver.caps->has_key('codeLensProvider')
+ lspserver.isCodeLensProvider = true
+ if lspserver.caps.codeLensProvider->has_key('resolveProvider')
+ lspserver.isCodeLensResolveProvider = true
+ else
+ lspserver.isCodeLensResolveProvider = false
+ endif
+ else
+ lspserver.isCodeLensProvider = false
+ endif
+
+ # workspaceSymbolProvider
+ if lspserver.caps->has_key('workspaceSymbolProvider')
+ if lspserver.caps.workspaceSymbolProvider->type() == v:t_bool
+ lspserver.isWorkspaceSymbolProvider =
+ lspserver.caps.workspaceSymbolProvider
+ else
+ lspserver.isWorkspaceSymbolProvider = true
+ endif
+ else
+ lspserver.isWorkspaceSymbolProvider = false
+ endif
+
+ # selectionRangeProvider
+ if lspserver.caps->has_key('selectionRangeProvider')
+ if lspserver.caps.selectionRangeProvider->type() == v:t_bool
+ lspserver.isSelectionRangeProvider =
+ lspserver.caps.selectionRangeProvider
+ else
+ lspserver.isSelectionRangeProvider = true
+ endif
+ else
+ lspserver.isSelectionRangeProvider = false
+ endif
+
+ # foldingRangeProvider
+ if lspserver.caps->has_key('foldingRangeProvider')
+ if lspserver.caps.foldingRangeProvider->type() == v:t_bool
+ lspserver.isFoldingRangeProvider = lspserver.caps.foldingRangeProvider
+ else
+ lspserver.isFoldingRangeProvider = true
+ endif
+ else
+ lspserver.isFoldingRangeProvider = false
+ endif
+
+ # inlayHintProvider
+ if lspserver.caps->has_key('inlayHintProvider')
+ if lspserver.caps.inlayHintProvider->type() == v:t_bool
+ lspserver.isInlayHintProvider = lspserver.caps.inlayHintProvider
+ else
+ lspserver.isInlayHintProvider = true
+ endif
+ else
+ lspserver.isInlayHintProvider = false
+ endif
+
+ # clangdInlayHintsProvider
+ if lspserver.caps->has_key('clangdInlayHintsProvider')
+ lspserver.isClangdInlayHintsProvider =
+ lspserver.caps.clangdInlayHintsProvider
+ else
+ lspserver.isClangdInlayHintsProvider = false
+ endif
+
+ # textDocumentSync capabilities
+ lspserver.supportsDidSave = false
+ # Default to TextDocumentSyncKind.None
+ lspserver.textDocumentSync = 0
+ if lspserver.caps->has_key('textDocumentSync')
+ if lspserver.caps.textDocumentSync->type() == v:t_bool
+ || lspserver.caps.textDocumentSync->type() == v:t_number
+ lspserver.supportsDidSave = lspserver.caps.textDocumentSync
+ lspserver.textDocumentSync = lspserver.caps.textDocumentSync
+ elseif lspserver.caps.textDocumentSync->type() == v:t_dict
+ # "save"
+ if lspserver.caps.textDocumentSync->has_key('save')
+ if lspserver.caps.textDocumentSync.save->type() == v:t_bool
+ || lspserver.caps.textDocumentSync.save->type() == v:t_number
+ lspserver.supportsDidSave = lspserver.caps.textDocumentSync.save
+ elseif lspserver.caps.textDocumentSync.save->type() == v:t_dict
+ lspserver.supportsDidSave = true
+ endif
+ endif
+ # "change"
+ if lspserver.caps.textDocumentSync->has_key('change')
+ lspserver.textDocumentSync = lspserver.caps.textDocumentSync.change
+ endif
+ endif
+ endif
+enddef
+
+# Return all the LSP client capabilities
+export def GetClientCaps(): dict<any>
+ # client capabilities (ClientCapabilities)
+ var clientCaps: dict<any> = {
+ general: {
+ # Currently we always send character count as position offset,
+ # which meanas only utf-32 is supported.
+ # Adding utf-16 simply for good mesure, as I'm scared some servers will
+ # give up if they don't support utf-32 only.
+ positionEncodings: ['utf-32', 'utf-16']
+ },
+ textDocument: {
+ callHierarchy: {
+ dynamicRegistration: false
+ },
+ codeAction: {
+ dynamicRegistration: false,
+ codeActionLiteralSupport: {
+ codeActionKind: {
+ valueSet: ['', 'quickfix', 'refactor', 'refactor.extract',
+ 'refactor.inline', 'refactor.rewrite', 'source',
+ 'source.organizeImports']
+ }
+ },
+ isPreferredSupport: true,
+ disabledSupport: true
+ },
+ codeLens: {
+ dynamicRegistration: false
+ },
+ completion: {
+ dynamicRegistration: false,
+ completionItem: {
+ documentationFormat: ['markdown', 'plaintext'],
+ resolveSupport: {
+ properties: ['detail', 'documentation']
+ },
+ snippetSupport: opt.lspOptions.snippetSupport,
+ insertReplaceSupport: false
+ },
+ completionItemKind: {
+ valueSet: range(1, 25)
+ }
+ },
+ declaration: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ definition: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ documentHighlight: {
+ dynamicRegistration: false
+ },
+ documentSymbol: {
+ dynamicRegistration: false,
+ symbolKind: {
+ valueSet: range(1, 25)
+ },
+ hierarchicalDocumentSymbolSupport: true,
+ labelSupport: false
+ },
+ foldingRange: {
+ dynamicRegistration: false,
+ rangeLimit: 5000,
+ lineFoldingOnly: true,
+ foldingRangeKind: {
+ valueSet: ['comment', 'imports', 'region']
+ },
+ foldingRange: {
+ collapsedText: true
+ }
+ },
+ formatting: {
+ dynamicRegistration: false
+ },
+ hover: {
+ dynamicRegistration: false,
+ contentFormat: ['markdown', 'plaintext']
+ },
+ implementation: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ inlayHint: {
+ dynamicRegistration: false
+ },
+ publishDiagnostics: {
+ relatedInformation: false,
+ versionSupport: true,
+ codeDescriptionSupport: true,
+ dataSupport: true
+ },
+ rangeFormatting: {
+ dynamicRegistration: false
+ },
+ references: {
+ dynamicRegistration: false
+ },
+ rename: {
+ dynamicRegistration: false,
+ prepareSupport: false,
+ },
+ selectionRange: {
+ dynamicRegistration: false,
+ },
+ signatureHelp: {
+ dynamicRegistration: false,
+ signatureInformation: {
+ documentationFormat: ['markdown', 'plaintext'],
+ activeParameterSupport: true
+ }
+ },
+ semanticTokens: {
+ dynamicRegistration: false,
+ requests: {
+ range: false,
+ full: {
+ delta: true
+ }
+ },
+ tokenTypes: [
+ 'type', 'class', 'enum', 'interface', 'struct', 'typeParameter',
+ 'parameter', 'variable', 'property', 'enumMember', 'event',
+ 'function', 'method', 'macro', 'keyword', 'modifier', 'comment',
+ 'string', 'number', 'regexp', 'operator'
+ ],
+ tokenModifiers: [
+ 'declaration', 'definition', 'readonly', 'static', 'deprecated',
+ 'abstract', 'async', 'modification', 'documentation',
+ 'defaultLibrary'
+ ],
+ formats: ['relative'],
+ overlappingTokenSupport: false,
+ multilineTokenSupport: false,
+ serverCancelSupport: false,
+ augmentsSyntaxTokens: true
+ },
+ synchronization: {
+ dynamicRegistration: false,
+ didSave: true,
+ willSave: false,
+ WillSaveWaitUntil: false
+ },
+ typeDefinition: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ typeHierarchy: {
+ dynamicRegistration: false,
+ }
+ },
+ window: {},
+ workspace: {
+ workspaceFolders: true,
+ applyEdit: true,
+ workspaceEdit: {
+ resourceOperations: ['rename', 'create', 'delete']
+ },
+ configuration: true,
+ symbol: {
+ dynamicRegistration: false
+ }
+ },
+ # This is the way clangd expects to be informated about supported encodings:
+ # https://clangd.llvm.org/extensions#utf-8-offsets
+ offsetEncoding: ['utf-32', 'utf-16']
+ }
+
+ # Vim patch 1629 is needed to properly encode/decode UTF-16 offsets
+ if has('patch-9.0.1629')
+ clientCaps.general.positionEncodings = ['utf-32', 'utf-16', 'utf-8']
+ clientCaps.offsetEncoding = ['utf-32', 'utf-16', 'utf-8']
+ endif
+
+ return clientCaps
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim
new file mode 100644
index 0000000..fb2a91a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim
@@ -0,0 +1,135 @@
+vim9script
+
+# Functions related to handling LSP code actions to fix diagnostics.
+
+import './util.vim'
+import './textedit.vim'
+import './options.vim' as opt
+
+var CommandHandlers: dict<func>
+
+export def RegisterCmdHandler(cmd: string, Handler: func)
+ CommandHandlers[cmd] = Handler
+enddef
+
+export def DoCommand(lspserver: dict<any>, cmd: dict<any>)
+ if cmd->has_key('command') && CommandHandlers->has_key(cmd.command)
+ var CmdHandler: func = CommandHandlers[cmd.command]
+ try
+ call CmdHandler(cmd)
+ catch
+ util.ErrMsg($'"{cmd.command}" handler raised exception {v:exception}')
+ endtry
+ else
+ lspserver.executeCommand(cmd)
+ endif
+enddef
+
+# Apply the code action selected by the user.
+export def HandleCodeAction(lspserver: dict<any>, selAction: dict<any>)
+ # textDocument/codeAction can return either Command[] or CodeAction[].
+ # If it is a CodeAction, it can have either an edit, a command or both.
+ # Edits should be executed first.
+ # Both Command and CodeAction interfaces has "command" member
+ # so we should check "command" type - for Command it will be "string"
+ if selAction->has_key('edit')
+ || (selAction->has_key('command') && selAction.command->type() == v:t_dict)
+ # selAction is a CodeAction instance, apply edit and command
+ if selAction->has_key('edit')
+ # apply edit first
+ textedit.ApplyWorkspaceEdit(selAction.edit)
+ endif
+ if selAction->has_key('command')
+ DoCommand(lspserver, selAction.command)
+ endif
+ else
+ # selAction is a Command instance, apply it directly
+ DoCommand(lspserver, selAction)
+ endif
+enddef
+
+# Process the list of code actions returned by the LSP server, ask the user to
+# choose one action from the list and then apply it.
+# If "query" is a number, then apply the corresponding action in the list.
+# If "query" is a regular expression starting with "/", then apply the action
+# matching the search string in the list.
+# If "query" is a regular string, then apply the action matching the string.
+# If "query" is an empty string, then if the "usePopupInCodeAction" option is
+# configured by the user, then display the list of items in a popup menu.
+# Otherwise display the items in an input list and prompt the user to select
+# an action.
+export def ApplyCodeAction(lspserver: dict<any>, actionlist: list<dict<any>>, query: string): void
+ var actions = actionlist
+
+ if opt.lspOptions.hideDisabledCodeActions
+ actions = actions->filter((ix, act) => !act->has_key('disabled'))
+ endif
+
+ if actions->empty()
+ # no action can be performed
+ util.WarnMsg('No code action is available')
+ return
+ endif
+
+ var text: list<string> = []
+ var act: dict<any>
+ for i in actions->len()->range()
+ act = actions[i]
+ var t: string = act.title->substitute('\r\n', '\\r\\n', 'g')
+ t = t->substitute('\n', '\\n', 'g')
+ text->add(printf(" %d. %s ", i + 1, t))
+ endfor
+
+ var choice: number
+
+ var query_ = query->trim()
+ if query_ =~ '^\d\+$' # digit
+ choice = query_->str2nr()
+ elseif query_ =~ '^/' # regex
+ choice = 1 + util.Indexof(actions, (i, a) => a.title =~ query_[1 : ])
+ elseif query_ != '' # literal string
+ choice = 1 + util.Indexof(actions, (i, a) => a.title[0 : query_->len() - 1] == query_)
+ elseif opt.lspOptions.usePopupInCodeAction
+ # Use a popup menu to show the code action
+ popup_create(text, {
+ pos: 'botleft',
+ line: 'cursor-1',
+ col: 'cursor',
+ zindex: 1000,
+ cursorline: 1,
+ mapping: 0,
+ wrap: 0,
+ title: 'Code action',
+ callback: (_, result) => {
+ # Invalid item selected or closed the popup
+ if result <= 0 || result > text->len()
+ return
+ endif
+
+ # Do the code action
+ HandleCodeAction(lspserver, actions[result - 1])
+ },
+ filter: (winid, key) => {
+ if key == 'h' || key == 'l'
+ winid->popup_close(-1)
+ elseif key->str2nr() > 0
+ # assume less than 10 entries are present
+ winid->popup_close(key->str2nr())
+ else
+ return popup_filter_menu(winid, key)
+ endif
+ return 1
+ },
+ })
+ else
+ choice = inputlist(['Code action:'] + text)
+ endif
+
+ if choice < 1 || choice > text->len()
+ return
+ endif
+
+ HandleCodeAction(lspserver, actions[choice - 1])
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim
new file mode 100644
index 0000000..082edf7
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim
@@ -0,0 +1,32 @@
+vim9script
+
+import './codeaction.vim'
+
+# Functions related to handling LSP code lens
+
+export def ProcessCodeLens(lspserver: dict<any>, bnr: number, codeLensItems: list<dict<any>>)
+ var text: list<string> = []
+ for i in codeLensItems->len()->range()
+ var item = codeLensItems[i]
+ if !item->has_key('command')
+ # resolve the code lens
+ item = lspserver.resolveCodeLens(bnr, item)
+ if item->empty()
+ continue
+ endif
+ codeLensItems[i] = item
+ endif
+ text->add(printf("%d. %s\t| L%s:%s", i + 1, item.command.title,
+ item.range.start.line + 1,
+ getline(item.range.start.line + 1)))
+ endfor
+
+ var choice = inputlist(['Code Lens:'] + text)
+ if choice < 1 || choice > codeLensItems->len()
+ return
+ endif
+
+ codeaction.DoCommand(lspserver, codeLensItems[choice - 1].command)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim
new file mode 100644
index 0000000..8ce5899
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim
@@ -0,0 +1,708 @@
+vim9script
+
+# LSP completion related functions
+
+import './util.vim'
+import './buffer.vim' as buf
+import './options.vim' as opt
+import './textedit.vim'
+import './snippet.vim'
+import './codeaction.vim'
+
+# per-filetype omni-completion enabled/disabled table
+var ftypeOmniCtrlMap: dict<bool> = {}
+
+var defaultKinds: dict<string> = {
+ 'Text': 't',
+ 'Method': 'm',
+ 'Function': 'f',
+ 'Constructor': 'C',
+ 'Field': 'F',
+ 'Variable': 'v',
+ 'Class': 'c',
+ 'Interface': 'i',
+ 'Module': 'M',
+ 'Property': 'p',
+ 'Unit': 'u',
+ 'Value': 'V',
+ 'Enum': 'e',
+ 'Keyword': 'k',
+ 'Snippet': 'S',
+ 'Color': 'C',
+ 'File': 'f',
+ 'Reference': 'r',
+ 'Folder': 'F',
+ 'EnumMember': 'E',
+ 'Constant': 'd',
+ 'Struct': 's',
+ 'Event': 'E',
+ 'Operator': 'o',
+ 'TypeParameter': 'T',
+ 'Buffer': 'B',
+}
+
+# Returns true if omni-completion is enabled for filetype "ftype".
+# Otherwise, returns false.
+def LspOmniComplEnabled(ftype: string): bool
+ return ftypeOmniCtrlMap->get(ftype, false)
+enddef
+
+# Enables or disables omni-completion for filetype "fype"
+export def OmniComplSet(ftype: string, enabled: bool)
+ ftypeOmniCtrlMap->extend({[ftype]: enabled})
+enddef
+
+# Map LSP complete item kind to a character
+def LspCompleteItemKindChar(kind: number): string
+ var kindMap: list<string> = [
+ '',
+ 'Text',
+ 'Method',
+ 'Function',
+ 'Constructor',
+ 'Field',
+ 'Variable',
+ 'Class',
+ 'Interface',
+ 'Module',
+ 'Property',
+ 'Unit',
+ 'Value',
+ 'Enum',
+ 'Keyword',
+ 'Snippet',
+ 'Color',
+ 'File',
+ 'Reference',
+ 'Folder',
+ 'EnumMember',
+ 'Constant',
+ 'Struct',
+ 'Event',
+ 'Operator',
+ 'TypeParameter',
+ 'Buffer'
+ ]
+
+ if kind > 26
+ return ''
+ endif
+
+ var kindName = kindMap[kind]
+ var kindValue = defaultKinds[kindName]
+
+ var lspOpts = opt.lspOptions
+ if lspOpts.customCompletionKinds &&
+ lspOpts.completionKinds->has_key(kindName)
+ kindValue = lspOpts.completionKinds[kindName]
+ endif
+
+ return kindValue
+enddef
+
+# Remove all the snippet placeholders from "str" and return the value.
+# Based on a similar function in the vim-lsp plugin.
+def MakeValidWord(str_arg: string): string
+ var str = str_arg->substitute('\$[0-9]\+\|\${\%(\\.\|[^}]\)\+}', '', 'g')
+ str = str->substitute('\\\(.\)', '\1', 'g')
+ var valid = str->matchstr('^[^"'' (<{\[\t\r\n]\+')
+ if valid->empty()
+ return str
+ endif
+ if valid =~ ':$'
+ return valid[: -2]
+ endif
+ return valid
+enddef
+
+# add completion from current buf
+def CompletionFromBuffer(items: list<dict<any>>)
+ var words = {}
+ var start = reltime()
+ var timeout = opt.lspOptions.bufferCompletionTimeout
+ var linenr = 1
+ for line in getline(1, '$')
+ for word in line->split('\W\+')
+ if !words->has_key(word) && word->len() > 1
+ words[word] = 1
+ items->add({
+ label: word,
+ data: {
+ entryNames: [word],
+ },
+ kind: 26,
+ documentation: "",
+ })
+ endif
+ endfor
+ # Check every 200 lines if timeout is exceeded
+ if timeout > 0 && linenr % 200 == 0 &&
+ start->reltime()->reltimefloat() * 1000 > timeout
+ break
+ endif
+ linenr += 1
+ endfor
+enddef
+
+# process the 'textDocument/completion' reply from the LSP server
+# Result: CompletionItem[] | CompletionList | null
+export def CompletionReply(lspserver: dict<any>, cItems: any)
+ lspserver.completeItemsIsIncomplete = false
+ if cItems->empty()
+ if lspserver.omniCompletePending
+ lspserver.completeItems = []
+ lspserver.omniCompletePending = false
+ endif
+ return
+ endif
+
+ var items: list<dict<any>>
+ if cItems->type() == v:t_list
+ items = cItems
+ else
+ items = cItems.items
+ lspserver.completeItemsIsIncomplete = cItems->get('isIncomplete', false)
+ endif
+
+ var lspOpts = opt.lspOptions
+
+ # Get the keyword prefix before the current cursor column.
+ var chcol = charcol('.')
+ var starttext = chcol == 1 ? '' : getline('.')[ : chcol - 2]
+ var [prefix, start_idx, end_idx] = starttext->matchstrpos('\k*$')
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ prefix = prefix->tolower()
+ endif
+
+ var start_col = start_idx + 1
+
+ if lspOpts.ultisnipsSupport
+ snippet.CompletionUltiSnips(prefix, items)
+ elseif lspOpts.vsnipSupport
+ snippet.CompletionVsnip(items)
+ endif
+
+ if lspOpts.useBufferCompletion
+ CompletionFromBuffer(items)
+ endif
+
+ var completeItems: list<dict<any>> = []
+ var itemsUsed: list<string> = []
+ for item in items
+ var d: dict<any> = {}
+
+ # TODO: Add proper support for item.textEdit.newText and
+ # item.textEdit.range. Keep in mind that item.textEdit.range can start
+ # way before the typed keyword.
+ if item->has_key('textEdit') &&
+ lspOpts.completionMatcherValue != opt.COMPLETIONMATCHER_FUZZY
+ var start_charcol: number
+ if !prefix->empty()
+ start_charcol = charidx(starttext, start_idx) + 1
+ else
+ start_charcol = chcol
+ endif
+ var textEdit = item.textEdit
+ var textEditRange: dict<any> = {}
+ if textEdit->has_key('range')
+ textEditRange = textEdit.range
+ elseif textEdit->has_key('insert')
+ textEditRange = textEdit.insert
+ endif
+ var textEditStartCol =
+ util.GetCharIdxWithoutCompChar(bufnr(), textEditRange.start)
+ if textEditStartCol != start_charcol
+ var offset = start_charcol - textEditStartCol - 1
+ d.word = textEdit.newText[offset : ]
+ else
+ d.word = textEdit.newText
+ endif
+ elseif item->has_key('insertText')
+ d.word = item.insertText
+ else
+ d.word = item.label
+ endif
+
+ if item->get('insertTextFormat', 1) == 2
+ # snippet completion. Needs a snippet plugin to expand the snippet.
+ # Remove all the snippet placeholders
+ d.word = MakeValidWord(d.word)
+ elseif !lspserver.completeItemsIsIncomplete || lspOpts.useBufferCompletion
+ # Filter items only when "isIncomplete" is set (otherwise server would
+ # have done the filtering) or when buffer completion is enabled
+
+ # plain text completion
+ if !prefix->empty()
+ # If the completion item text doesn't start with the current (case
+ # ignored) keyword prefix, skip it.
+ var filterText: string = item->get('filterText', d.word)
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ if filterText->tolower()->stridx(prefix) != 0
+ continue
+ endif
+ # If the completion item text doesn't fuzzy match with the current
+ # keyword prefix, skip it.
+ elseif lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_FUZZY
+ if matchfuzzy([filterText], prefix)->empty()
+ continue
+ endif
+ # If the completion item text doesn't start with the current keyword
+ # prefix, skip it.
+ else
+ if filterText->stridx(prefix) != 0
+ continue
+ endif
+ endif
+ endif
+ endif
+
+ d.abbr = item.label
+ d.dup = 1
+
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ d.icase = 1
+ endif
+
+ if item->has_key('kind') && item.kind != null
+ # namespace CompletionItemKind
+ # map LSP kind to complete-item-kind
+ d.kind = LspCompleteItemKindChar(item.kind)
+ endif
+
+ if lspserver.completionLazyDoc
+ d.info = 'Lazy doc'
+ else
+ if item->has_key('detail') && !item.detail->empty()
+ # Solve a issue where if a server send a detail field
+ # with a "\n", on the menu will be everything joined with
+ # a "^@" separating it. (example: clangd)
+ d.menu = item.detail->split("\n")[0]
+ endif
+ if item->has_key('documentation')
+ var itemDoc = item.documentation
+ if itemDoc->type() == v:t_string && !itemDoc->empty()
+ d.info = itemDoc
+ elseif itemDoc->type() == v:t_dict
+ && itemDoc.value->type() == v:t_string
+ d.info = itemDoc.value
+ endif
+ endif
+ endif
+
+ # Score is used for sorting.
+ d.score = item->get('sortText')
+ if d.score->empty()
+ d.score = item->get('label', '')
+ endif
+
+ # Dont include duplicate items
+ if lspOpts.filterCompletionDuplicates
+ var key = d->get('word', '') ..
+ d->get('info', '') ..
+ d->get('kind', '') ..
+ d->get('score', '') ..
+ d->get('abbr', '') ..
+ d->get('dup', '')
+ if index(itemsUsed, key) != -1
+ continue
+ endif
+ add(itemsUsed, key)
+ endif
+
+ d.user_data = item
+ completeItems->add(d)
+ endfor
+
+ if lspOpts.completionMatcherValue != opt.COMPLETIONMATCHER_FUZZY
+ # Lexographical sort (case-insensitive).
+ completeItems->sort((a, b) =>
+ a.score == b.score ? 0 : a.score >? b.score ? 1 : -1)
+ endif
+
+ if lspOpts.autoComplete && !lspserver.omniCompletePending
+ if completeItems->empty()
+ # no matches
+ return
+ endif
+
+ var m = mode()
+ if m != 'i' && m != 'R' && m != 'Rv'
+ # If not in insert or replace mode, then don't start the completion
+ return
+ endif
+
+ if completeItems->len() == 1
+ && getline('.')->matchstr($'\C{completeItems[0].word}\>') != ''
+ # only one complete match. No need to show the completion popup
+ return
+ endif
+
+ completeItems->complete(start_col)
+ else
+ lspserver.completeItems = completeItems
+ lspserver.omniCompletePending = false
+ endif
+enddef
+
+# Check if completion item is selected
+def CheckCompletionItemSel(label: string): bool
+ var cInfo = complete_info()
+ if cInfo->empty() || !cInfo.pum_visible || cInfo.selected == -1
+ return false
+ endif
+ var selItem = cInfo.items->get(cInfo.selected, {})
+ if selItem->empty()
+ || selItem->type() != v:t_dict
+ || selItem.user_data->type() != v:t_dict
+ || selItem.user_data.label != label
+ return false
+ endif
+ return true
+enddef
+
+# Process the completion documentation
+def ShowCompletionDocumentation(cItem: any)
+ if cItem->empty() || cItem->type() != v:t_dict
+ return
+ endif
+
+ # check if completion item is still selected
+ if !CheckCompletionItemSel(cItem.label)
+ return
+ endif
+
+ var infoText: list<string>
+ var infoKind: string
+
+ if cItem->has_key('detail') && !cItem.detail->empty()
+ # Solve a issue where if a server send the detail field with "\n",
+ # on the completion popup, everything will be joined with "^@"
+ # (example: typescript-language-server)
+ infoText->extend(cItem.detail->split("\n"))
+ endif
+
+ if cItem->has_key('documentation')
+ if !infoText->empty()
+ infoText->extend(['- - -'])
+ endif
+ var cItemDoc = cItem.documentation
+ if cItemDoc->type() == v:t_dict
+ # MarkupContent
+ if cItemDoc.kind == 'plaintext'
+ infoText->extend(cItemDoc.value->split("\n"))
+ infoKind = 'text'
+ elseif cItemDoc.kind == 'markdown'
+ infoText->extend(cItemDoc.value->split("\n"))
+ infoKind = 'lspgfm'
+ else
+ util.ErrMsg($'Unsupported documentation type ({cItemDoc.kind})')
+ return
+ endif
+ elseif cItemDoc->type() == v:t_string
+ infoText->extend(cItemDoc->split("\n"))
+ else
+ util.ErrMsg($'Unsupported documentation ({cItemDoc->string()})')
+ return
+ endif
+ endif
+
+ if infoText->empty()
+ return
+ endif
+
+ # check if completion item is changed in meantime
+ if !CheckCompletionItemSel(cItem.label)
+ return
+ endif
+
+ # autoComplete or &omnifunc with &completeopt =~ 'popup'
+ var id = popup_findinfo()
+ if id > 0
+ var bufnr = id->winbufnr()
+ id->popup_settext(infoText)
+ infoKind->setbufvar(bufnr, '&ft')
+ id->popup_show()
+ else
+ # &omnifunc with &completeopt =~ 'preview'
+ try
+ :wincmd P
+ :setlocal modifiable
+ bufnr()->deletebufline(1, '$')
+ infoText->append(0)
+ [1, 1]->cursor()
+ exe $'setlocal ft={infoKind}'
+ :wincmd p
+ catch /E441/ # No preview window
+ endtry
+ endif
+enddef
+
+# process the 'completionItem/resolve' reply from the LSP server
+# Result: CompletionItem
+export def CompletionResolveReply(lspserver: dict<any>, cItem: any)
+ ShowCompletionDocumentation(cItem)
+enddef
+
+# Return trigger kind and trigger char. If completion trigger is not a keyword
+# and not one of the triggerCharacters, return -1 for triggerKind.
+def GetTriggerAttributes(lspserver: dict<any>): list<any>
+ var triggerKind: number = 1
+ var triggerChar: string = ''
+
+ # Trigger kind is 1 for keyword and 2 for trigger char initiated completion.
+ var line: string = getline('.')
+ var cur_col = charcol('.')
+ if line[cur_col - 2] !~ '\k'
+ var trigChars = lspserver.completionTriggerChars
+ var trigidx = trigChars->index(line[cur_col - 2])
+ if trigidx == -1
+ triggerKind = -1
+ else
+ triggerKind = 2
+ triggerChar = trigChars[trigidx]
+ endif
+ endif
+ return [triggerKind, triggerChar]
+enddef
+
+
+# omni complete handler
+def g:LspOmniFunc(findstart: number, base: string): any
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('completion')
+ if lspserver->empty()
+ return -2
+ endif
+
+ if findstart
+
+ var [triggerKind, triggerChar] = GetTriggerAttributes(lspserver)
+ if triggerKind < 0
+ # previous character is not a keyword character or a trigger character,
+ # so cancel omni completion.
+ return -2
+ endif
+
+ # first send all the changes in the current buffer to the LSP server
+ listener_flush()
+
+ lspserver.omniCompletePending = true
+ lspserver.completeItems = []
+
+ # initiate a request to LSP server to get list of completions
+ lspserver.getCompletion(triggerKind, triggerChar)
+
+ # locate the start of the word
+ var line = getline('.')->strpart(0, col('.') - 1)
+ var keyword = line->matchstr('\k\+$')
+ lspserver.omniCompleteKeyword = keyword
+ return line->len() - keyword->len()
+ else
+ # Wait for the list of matches from the LSP server
+ var count: number = 0
+ while lspserver.omniCompletePending && count < 1000
+ if complete_check()
+ return v:none
+ endif
+ sleep 2m
+ count += 1
+ endwhile
+
+ if lspserver.omniCompletePending
+ return v:none
+ endif
+
+ var res: list<dict<any>> = lspserver.completeItems
+ var prefix = lspserver.omniCompleteKeyword
+
+ # Don't attempt to filter on the items, when "isIncomplete" is set
+ if prefix->empty() || lspserver.completeItemsIsIncomplete
+ return res
+ endif
+
+ var lspOpts = opt.lspOptions
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_FUZZY
+ return res->matchfuzzy(prefix, { key: 'word' })
+ endif
+
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ return res->filter((i, v) =>
+ v.word->tolower()->stridx(prefix->tolower()) == 0)
+ endif
+
+ return res->filter((i, v) => v.word->stridx(prefix) == 0)
+ endif
+enddef
+
+# For plugins that implement async completion this function indicates if
+# omnifunc is waiting for LSP response.
+def g:LspOmniCompletePending(): bool
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('completion')
+ return !lspserver->empty() && lspserver.omniCompletePending
+enddef
+
+# Insert mode completion handler. Used when 24x7 completion is enabled
+# (default).
+def LspComplete()
+ var lspserver: dict<any> = buf.CurbufGetServer('completion')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ var [triggerKind, triggerChar] = GetTriggerAttributes(lspserver)
+ if triggerKind < 0
+ return
+ endif
+
+ # first send all the changes in the current buffer to the LSP server
+ listener_flush()
+
+ # initiate a request to LSP server to get list of completions
+ lspserver.getCompletion(triggerKind, triggerChar)
+enddef
+
+# Lazy complete documentation handler
+def LspResolve()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('completion')
+ if lspserver->empty()
+ return
+ endif
+
+ var item = v:event.completed_item
+ if item->has_key('user_data') && !item.user_data->empty()
+ if item.user_data->type() == v:t_dict && !item.user_data->has_key('documentation')
+ lspserver.resolveCompletion(item.user_data)
+ else
+ ShowCompletionDocumentation(item.user_data)
+ endif
+ endif
+enddef
+
+# If the completion popup documentation window displays "markdown" content,
+# then set the 'filetype' to "lspgfm".
+def LspSetPopupFileType()
+ var item = v:event.completed_item
+ var cItem = item->get('user_data', {})
+ if cItem->empty()
+ return
+ endif
+
+ if cItem->type() != v:t_dict || !cItem->has_key('documentation')
+ \ || cItem.documentation->type() != v:t_dict
+ \ || cItem.documentation.kind != 'markdown'
+ return
+ endif
+
+ var id = popup_findinfo()
+ if id > 0
+ var bnum = id->winbufnr()
+ setbufvar(bnum, '&ft', 'lspgfm')
+ endif
+enddef
+
+# complete done handler (LSP server-initiated actions after completion)
+def LspCompleteDone(bnr: number)
+ var lspserver: dict<any> = buf.BufLspServerGet(bnr, 'completion')
+ if lspserver->empty()
+ return
+ endif
+
+ if v:completed_item->type() != v:t_dict
+ return
+ endif
+
+ var completionData: any = v:completed_item->get('user_data', '')
+ if completionData->type() != v:t_dict
+ || !opt.lspOptions.completionTextEdit
+ return
+ endif
+
+ if !completionData->has_key('additionalTextEdits')
+ # Some language servers (e.g. typescript) delay the computation of the
+ # additional text edits. So try to resolve the completion item now to get
+ # the text edits.
+ completionData = lspserver.resolveCompletion(completionData, true)
+ endif
+ if !completionData->get('additionalTextEdits', {})->empty()
+ textedit.ApplyTextEdits(bnr, completionData.additionalTextEdits)
+ endif
+
+ if completionData->has_key('command')
+ # Some language servers (e.g. haskell-language-server) want to apply
+ # additional commands after completion.
+ codeaction.DoCommand(lspserver, completionData.command)
+ endif
+
+enddef
+
+# Initialize buffer-local completion options and autocmds
+export def BufferInit(lspserver: dict<any>, bnr: number, ftype: string)
+ if !lspserver.isCompletionProvider
+ # no support for completion
+ return
+ endif
+
+ if !opt.lspOptions.autoComplete && !LspOmniComplEnabled(ftype) && !opt.lspOptions.omniComplete
+ # LSP auto/omni completion support is not enabled for this buffer
+ return
+ endif
+
+ # buffer-local autocmds for completion
+ var acmds: list<dict<any>> = []
+
+ # set options for insert mode completion
+ if opt.lspOptions.autoComplete
+ if lspserver.completionLazyDoc
+ setbufvar(bnr, '&completeopt', 'menuone,popuphidden,noinsert,noselect')
+ else
+ setbufvar(bnr, '&completeopt', 'menuone,popup,noinsert,noselect')
+ endif
+ setbufvar(bnr, '&completepopup',
+ 'width:80,highlight:Pmenu,align:item,border:off')
+ # <Enter> in insert mode stops completion and inserts a <Enter>
+ if !opt.lspOptions.noNewlineInCompletion
+ :inoremap <expr> <buffer> <CR> pumvisible() ? "\<C-Y>\<CR>" : "\<CR>"
+ endif
+
+ # Trigger 24x7 insert mode completion when text is changed
+ acmds->add({bufnr: bnr,
+ event: 'TextChangedI',
+ group: 'LSPBufferAutocmds',
+ cmd: 'LspComplete()'})
+ endif
+
+ if LspOmniComplEnabled(ftype)
+ setbufvar(bnr, '&omnifunc', 'g:LspOmniFunc')
+ endif
+
+ if lspserver.completionLazyDoc
+ # resolve additional documentation for a selected item
+ acmds->add({bufnr: bnr,
+ event: 'CompleteChanged',
+ group: 'LSPBufferAutocmds',
+ cmd: 'LspResolve()'})
+ endif
+
+ acmds->add({bufnr: bnr,
+ event: 'CompleteChanged',
+ group: 'LSPBufferAutocmds',
+ cmd: 'LspSetPopupFileType()'})
+
+ # Execute LSP server initiated text edits after completion
+ acmds->add({bufnr: bnr,
+ event: 'CompleteDone',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspCompleteDone({bnr})'})
+
+ autocmd_add(acmds)
+enddef
+
+# Buffer "bnr" is loaded in a window. If omni-completion is enabled for this
+# buffer, then set the 'omnifunc' option.
+export def BufferLoadedInWin(bnr: number)
+ if !opt.lspOptions.autoComplete
+ && LspOmniComplEnabled(bnr->getbufvar('&filetype'))
+ setbufvar(bnr, '&omnifunc', 'g:LspOmniFunc')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim
new file mode 100644
index 0000000..6975edf
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim
@@ -0,0 +1,920 @@
+vim9script
+
+# Functions related to handling LSP diagnostics.
+
+import './options.vim' as opt
+import './buffer.vim' as buf
+import './util.vim'
+
+# [bnr] = {
+# serverDiagnostics: {
+# lspServer1Id: [diag, diag, diag]
+# lspServer2Id: [diag, diag, diag]
+# },
+# serverDiagnosticsByLnum: {
+# lspServer1Id: { [lnum]: [diag, diag diag] },
+# lspServer2Id: { [lnum]: [diag, diag diag] },
+# },
+# sortedDiagnostics: [lspServer1.diags, ...lspServer2.diags]->sort()
+# }
+var diagsMap: dict<dict<any>> = {}
+
+# Initialize the signs and the text property type used for diagnostics.
+export def InitOnce()
+ # Signs and their highlight groups used for LSP diagnostics
+ hlset([
+ {name: 'LspDiagLine', default: true, linksto: 'NONE'},
+ {name: 'LspDiagSignErrorText', default: true, linksto: 'ErrorMsg'},
+ {name: 'LspDiagSignWarningText', default: true, linksto: 'Search'},
+ {name: 'LspDiagSignInfoText', default: true, linksto: 'Pmenu'},
+ {name: 'LspDiagSignHintText', default: true, linksto: 'Question'}
+ ])
+ sign_define([
+ {
+ name: 'LspDiagError',
+ text: opt.lspOptions.diagSignErrorText,
+ texthl: 'LspDiagSignErrorText',
+ linehl: 'LspDiagLine'
+ },
+ {
+ name: 'LspDiagWarning',
+ text: opt.lspOptions.diagSignWarningText,
+ texthl: 'LspDiagSignWarningText',
+ linehl: 'LspDiagLine'
+ },
+ {
+ name: 'LspDiagInfo',
+ text: opt.lspOptions.diagSignInfoText,
+ texthl: 'LspDiagSignInfoText',
+ linehl: 'LspDiagLine'
+ },
+ {
+ name: 'LspDiagHint',
+ text: opt.lspOptions.diagSignHintText,
+ texthl: 'LspDiagSignHintText',
+ linehl: 'LspDiagLine'
+ }
+ ])
+
+ # Diag inline highlight groups and text property types
+ hlset([
+ {name: 'LspDiagInlineError', default: true, linksto: 'SpellBad'},
+ {name: 'LspDiagInlineWarning', default: true, linksto: 'SpellCap'},
+ {name: 'LspDiagInlineInfo', default: true, linksto: 'SpellRare'},
+ {name: 'LspDiagInlineHint', default: true, linksto: 'SpellLocal'}
+ ])
+
+ var override = &cursorline
+ && &cursorlineopt =~ '\<line\>\|\<screenline\>\|\<both\>'
+
+ prop_type_add('LspDiagInlineError',
+ {highlight: 'LspDiagInlineError',
+ priority: 10,
+ override: override})
+ prop_type_add('LspDiagInlineWarning',
+ {highlight: 'LspDiagInlineWarning',
+ priority: 9,
+ override: override})
+ prop_type_add('LspDiagInlineInfo',
+ {highlight: 'LspDiagInlineInfo',
+ priority: 8,
+ override: override})
+ prop_type_add('LspDiagInlineHint',
+ {highlight: 'LspDiagInlineHint',
+ priority: 7,
+ override: override})
+
+ # Diag virtual text highlight groups and text property types
+ hlset([
+ {name: 'LspDiagVirtualTextError', default: true, linksto: 'SpellBad'},
+ {name: 'LspDiagVirtualTextWarning', default: true, linksto: 'SpellCap'},
+ {name: 'LspDiagVirtualTextInfo', default: true, linksto: 'SpellRare'},
+ {name: 'LspDiagVirtualTextHint', default: true, linksto: 'SpellLocal'},
+ ])
+ prop_type_add('LspDiagVirtualTextError',
+ {highlight: 'LspDiagVirtualTextError', override: true})
+ prop_type_add('LspDiagVirtualTextWarning',
+ {highlight: 'LspDiagVirtualTextWarning', override: true})
+ prop_type_add('LspDiagVirtualTextInfo',
+ {highlight: 'LspDiagVirtualTextInfo', override: true})
+ prop_type_add('LspDiagVirtualTextHint',
+ {highlight: 'LspDiagVirtualTextHint', override: true})
+
+ autocmd_add([{group: 'LspCmds',
+ event: 'User',
+ pattern: 'LspOptionsChanged',
+ cmd: 'LspDiagsOptionsChanged()'}])
+
+ # ALE plugin support
+ if opt.lspOptions.aleSupport
+ opt.lspOptions.autoHighlightDiags = false
+ autocmd_add([
+ {
+ group: 'LspAleCmds',
+ event: 'User',
+ pattern: 'ALEWantResults',
+ cmd: 'AleHook(g:ale_want_results_buffer)'
+ }
+ ])
+ endif
+enddef
+
+# Initialize the diagnostics features for the buffer 'bnr'
+export def BufferInit(lspserver: dict<any>, bnr: number)
+ if opt.lspOptions.showDiagInBalloon
+ :set ballooneval balloonevalterm
+ setbufvar(bnr, '&balloonexpr', 'g:LspDiagExpr()')
+ endif
+
+ var acmds: list<dict<any>> = []
+ # Show diagnostics on the status line
+ if opt.lspOptions.showDiagOnStatusLine
+ acmds->add({bufnr: bnr,
+ event: 'CursorMoved',
+ group: 'LSPBufferAutocmds',
+ cmd: 'ShowCurrentDiagInStatusLine()'})
+ endif
+ autocmd_add(acmds)
+enddef
+
+# Function to sort the diagnostics in ascending order based on the line and
+# character offset
+def DiagsSortFunc(a: dict<any>, b: dict<any>): number
+ var a_start: dict<number> = a.range.start
+ var b_start: dict<number> = b.range.start
+ var linediff: number = a_start.line - b_start.line
+ if linediff == 0
+ return a_start.character - b_start.character
+ endif
+ return linediff
+enddef
+
+# Sort diagnostics ascending based on line and character offset
+def SortDiags(diags: list<dict<any>>): list<dict<any>>
+ return diags->sort(DiagsSortFunc)
+enddef
+
+# Remove the diagnostics stored for buffer "bnr"
+export def DiagRemoveFile(bnr: number)
+ if diagsMap->has_key(bnr)
+ diagsMap->remove(bnr)
+ endif
+enddef
+
+def DiagSevToSignName(severity: number): string
+ var typeMap: list<string> = ['LspDiagError', 'LspDiagWarning',
+ 'LspDiagInfo', 'LspDiagHint']
+ if severity > 4
+ return 'LspDiagHint'
+ endif
+ return typeMap[severity - 1]
+enddef
+
+def DiagSevToInlineHLName(severity: number): string
+ var typeMap: list<string> = [
+ 'LspDiagInlineError',
+ 'LspDiagInlineWarning',
+ 'LspDiagInlineInfo',
+ 'LspDiagInlineHint'
+ ]
+ if severity > 4
+ return 'LspDiagInlineHint'
+ endif
+ return typeMap[severity - 1]
+enddef
+
+def DiagSevToVirtualTextHLName(severity: number): string
+ var typeMap: list<string> = [
+ 'LspDiagVirtualTextError',
+ 'LspDiagVirtualTextWarning',
+ 'LspDiagVirtualTextInfo',
+ 'LspDiagVirtualTextHint'
+ ]
+ if severity > 4
+ return 'LspDiagVirtualTextHint'
+ endif
+ return typeMap[severity - 1]
+enddef
+
+def DiagSevToSymbolText(severity: number): string
+ var lspOpts = opt.lspOptions
+ var typeMap: list<string> = [
+ lspOpts.diagSignErrorText,
+ lspOpts.diagSignWarningText,
+ lspOpts.diagSignInfoText,
+ lspOpts.diagSignHintText
+ ]
+ if severity > 4
+ return lspOpts.diagSignHintText
+ endif
+ return typeMap[severity - 1]
+enddef
+
+# Remove signs and text properties for diagnostics in buffer
+def RemoveDiagVisualsForBuffer(bnr: number, all: bool = false)
+ var lspOpts = opt.lspOptions
+ if lspOpts.showDiagWithSign || all
+ # Remove all the existing diagnostic signs
+ sign_unplace('LSPDiag', {buffer: bnr})
+ endif
+
+ if lspOpts.showDiagWithVirtualText || all
+ # Remove all the existing virtual text
+ prop_remove({type: 'LspDiagVirtualTextError', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagVirtualTextWarning', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagVirtualTextInfo', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagVirtualTextHint', bufnr: bnr, all: true})
+ endif
+
+ if lspOpts.highlightDiagInline || all
+ # Remove all the existing virtual text
+ prop_remove({type: 'LspDiagInlineError', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagInlineWarning', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagInlineInfo', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagInlineHint', bufnr: bnr, all: true})
+ endif
+enddef
+
+# Refresh the placed diagnostics in buffer "bnr"
+# This inline signs, inline props, and virtual text diagnostics
+export def DiagsRefresh(bnr: number, all: bool = false)
+ var lspOpts = opt.lspOptions
+ if !lspOpts.autoHighlightDiags
+ return
+ endif
+
+ :silent! bnr->bufload()
+
+ RemoveDiagVisualsForBuffer(bnr, all)
+
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ return
+ endif
+
+ # Initialize default/fallback properties for diagnostic virtual text:
+ var diag_align: string = 'above'
+ var diag_wrap: string = 'truncate'
+ var diag_symbol: string = '┌─'
+
+ if lspOpts.diagVirtualTextAlign == 'below'
+ diag_align = 'below'
+ diag_wrap = 'truncate'
+ diag_symbol = '└─'
+ elseif lspOpts.diagVirtualTextAlign == 'after'
+ diag_align = 'after'
+ diag_wrap = 'wrap'
+ diag_symbol = 'E>'
+ endif
+
+ if lspOpts.diagVirtualTextWrap != 'default'
+ diag_wrap = lspOpts.diagVirtualTextWrap
+ endif
+
+ var signs: list<dict<any>> = []
+ var diags: list<dict<any>> = diagsMap[bnr].sortedDiagnostics
+ var inlineHLprops: list<list<list<number>>> = [[], [], [], [], []]
+ for diag in diags
+ # TODO: prioritize most important severity if there are multiple
+ # diagnostics from the same line
+ var d_range = diag.range
+ var d_start = d_range.start
+ var d_end = d_range.end
+ var lnum = d_start.line + 1
+ if lspOpts.showDiagWithSign
+ signs->add({id: 0, buffer: bnr, group: 'LSPDiag',
+ lnum: lnum, name: DiagSevToSignName(diag.severity),
+ priority: 10 - diag.severity})
+ endif
+
+ try
+ if lspOpts.highlightDiagInline
+ var propLocation: list<number> = [
+ lnum, util.GetLineByteFromPos(bnr, d_start) + 1,
+ d_end.line + 1, util.GetLineByteFromPos(bnr, d_end) + 1
+ ]
+ inlineHLprops[diag.severity]->add(propLocation)
+ endif
+
+ if lspOpts.showDiagWithVirtualText
+ var padding: number
+ var symbol: string = diag_symbol
+
+ if diag_align == 'after'
+ padding = 3
+ symbol = DiagSevToSymbolText(diag.severity)
+ else
+ var charIdx = util.GetCharIdxWithoutCompChar(bnr, d_start)
+ padding = charIdx
+ if padding > 0
+ padding = strdisplaywidth(getline(lnum)[ : charIdx - 1])
+ endif
+ endif
+
+ prop_add(lnum, 0, {bufnr: bnr,
+ type: DiagSevToVirtualTextHLName(diag.severity),
+ text: $'{symbol} {diag.message}',
+ text_align: diag_align,
+ text_wrap: diag_wrap,
+ text_padding_left: padding})
+ endif
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ # Diagnostics arrive asynchronously and the document changed while they
+ # were in transit. Ignore this as new once will arrive shortly.
+ endtry
+ endfor
+
+ if lspOpts.highlightDiagInline
+ for i in range(1, 4)
+ if !inlineHLprops[i]->empty()
+ try
+ prop_add_list({bufnr: bnr, type: DiagSevToInlineHLName(i)},
+ inlineHLprops[i])
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ endtry
+ endif
+ endfor
+ endif
+
+ if lspOpts.showDiagWithSign
+ signs->sign_placelist()
+ endif
+enddef
+
+# Sends diagnostics to Ale
+def SendAleDiags(bnr: number, timerid: number)
+ if !diagsMap->has_key(bnr)
+ return
+ endif
+
+ # Convert to Ale's diagnostics format (:h ale-loclist-format)
+ ale#other_source#ShowResults(bnr, 'lsp',
+ diagsMap[bnr].sortedDiagnostics->mapnew((_, v) => {
+ return {text: v.message,
+ lnum: v.range.start.line + 1,
+ col: util.GetLineByteFromPos(bnr, v.range.start) + 1,
+ end_lnum: v.range.end.line + 1,
+ end_col: util.GetLineByteFromPos(bnr, v.range.end) + 1,
+ type: "EWIH"[v.severity - 1]}
+ })
+ )
+enddef
+
+# Hook called when Ale wants to retrieve new diagnostics
+def AleHook(bnr: number)
+ ale#other_source#StartChecking(bnr, 'lsp')
+ timer_start(0, function('SendAleDiags', [bnr]))
+enddef
+
+# New LSP diagnostic messages received from the server for a file.
+# Update the signs placed in the buffer for this file
+export def ProcessNewDiags(bnr: number)
+ DiagsUpdateLocList(bnr)
+
+ var lspOpts = opt.lspOptions
+ if lspOpts.aleSupport
+ SendAleDiags(bnr, -1)
+ endif
+
+ if bnr == -1 || !diagsMap->has_key(bnr)
+ return
+ endif
+
+ var curmode: string = mode()
+ if curmode == 'i' || curmode == 'R' || curmode == 'Rv'
+ # postpone placing signs in insert mode and replace mode. These will be
+ # placed after the user returns to Normal mode.
+ setbufvar(bnr, 'LspDiagsUpdatePending', true)
+ return
+ endif
+
+ DiagsRefresh(bnr)
+enddef
+
+# process a diagnostic notification message from the LSP server
+# Notification: textDocument/publishDiagnostics
+# Param: PublishDiagnosticsParams
+export def DiagNotification(lspserver: dict<any>, uri: string, diags_arg: list<dict<any>>): void
+ # Diagnostics are disabled for this server?
+ if !lspserver.featureEnabled('diagnostics')
+ return
+ endif
+
+ var fname: string = util.LspUriToFile(uri)
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ # Is this condition possible?
+ return
+ endif
+
+ var newDiags: list<dict<any>> = diags_arg
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the diags
+ newDiags->map((_, dval) => {
+ lspserver.decodeRange(bnr, dval.range)
+ return dval
+ })
+ endif
+
+ if lspserver.processDiagHandler != null_function
+ newDiags = lspserver.processDiagHandler(diags_arg)
+ endif
+
+ # TODO: Is the buffer (bnr) always a loaded buffer? Should we load it here?
+ var lastlnum: number = bnr->getbufinfo()[0].linecount
+
+ # store the diagnostic for each line separately
+ var diagsByLnum: dict<list<dict<any>>> = {}
+
+ var diagWithinRange: list<dict<any>> = []
+ for diag in newDiags
+ var d_start = diag.range.start
+ if d_start.line + 1 > lastlnum
+ # Make sure the line number is a valid buffer line number
+ d_start.line = lastlnum - 1
+ endif
+
+ var lnum = d_start.line + 1
+ if !diagsByLnum->has_key(lnum)
+ diagsByLnum[lnum] = []
+ endif
+ diagsByLnum[lnum]->add(diag)
+
+ diagWithinRange->add(diag)
+ endfor
+
+ var serverDiags: dict<list<any>> = diagsMap->has_key(bnr) ?
+ diagsMap[bnr].serverDiagnostics : {}
+ serverDiags[lspserver.id] = diagWithinRange
+
+ var serverDiagsByLnum: dict<dict<list<any>>> = diagsMap->has_key(bnr) ?
+ diagsMap[bnr].serverDiagnosticsByLnum : {}
+ serverDiagsByLnum[lspserver.id] = diagsByLnum
+
+ # store the diagnostic for each line separately
+ var joinedServerDiags: list<dict<any>> = []
+ for diags in serverDiags->values()
+ joinedServerDiags->extend(diags)
+ endfor
+
+ var sortedDiags = SortDiags(joinedServerDiags)
+
+ diagsMap[bnr] = {
+ sortedDiagnostics: sortedDiags,
+ serverDiagnosticsByLnum: serverDiagsByLnum,
+ serverDiagnostics: serverDiags
+ }
+
+ ProcessNewDiags(bnr)
+
+ # Notify user scripts that diags has been updated
+ if exists('#User#LspDiagsUpdated')
+ :doautocmd <nomodeline> User LspDiagsUpdated
+ endif
+enddef
+
+# get the count of error in the current buffer
+export def DiagsGetErrorCount(bnr: number): dict<number>
+ var diagSevCount: list<number> = [0, 0, 0, 0, 0]
+ if diagsMap->has_key(bnr)
+ var diags = diagsMap[bnr].sortedDiagnostics
+ for diag in diags
+ var severity = diag->get('severity', 0)
+ diagSevCount[severity] += 1
+ endfor
+ endif
+
+ return {
+ Error: diagSevCount[1],
+ Warn: diagSevCount[2],
+ Info: diagSevCount[3],
+ Hint: diagSevCount[4]
+ }
+enddef
+
+# Map the LSP DiagnosticSeverity to a quickfix type character
+def DiagSevToQfType(severity: number): string
+ var typeMap: list<string> = ['E', 'W', 'I', 'N']
+
+ if severity > 4
+ return ''
+ endif
+
+ return typeMap[severity - 1]
+enddef
+
+# Update the location list window for the current window with the diagnostic
+# messages.
+# Returns true if diagnostics is not empty and false if it is empty.
+def DiagsUpdateLocList(bnr: number, calledByCmd: bool = false): bool
+ var fname: string = bnr->bufname()->fnamemodify(':p')
+ if fname->empty()
+ return false
+ endif
+
+ var LspQfId: number = bnr->getbufvar('LspQfId', 0)
+ if LspQfId == 0 && !opt.lspOptions.autoPopulateDiags && !calledByCmd
+ # Diags location list is not present. Create the location list only if
+ # the 'autoPopulateDiags' option is set or the ":LspDiag show" command is
+ # invoked.
+ return false
+ endif
+
+ if LspQfId != 0 && getloclist(0, {id: LspQfId}).id != LspQfId
+ # Previously used location list for the diagnostics is gone
+ LspQfId = 0
+ endif
+
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ if LspQfId != 0
+ setloclist(0, [], 'r', {id: LspQfId, items: []})
+ endif
+ return false
+ endif
+
+ var qflist: list<dict<any>> = []
+ var text: string
+
+ var diags = diagsMap[bnr].sortedDiagnostics
+ for diag in diags
+ var d_range = diag.range
+ var d_start = d_range.start
+ var d_end = d_range.end
+ text = diag.message->substitute("\n\\+", "\n", 'g')
+ qflist->add({filename: fname,
+ lnum: d_start.line + 1,
+ col: util.GetLineByteFromPos(bnr, d_start) + 1,
+ end_lnum: d_end.line + 1,
+ end_col: util.GetLineByteFromPos(bnr, d_end) + 1,
+ text: text,
+ type: DiagSevToQfType(diag.severity)})
+ endfor
+
+ var op: string = ' '
+ var props = {title: 'Language Server Diagnostics', items: qflist}
+ if LspQfId != 0
+ op = 'r'
+ props.id = LspQfId
+ endif
+ setloclist(0, [], op, props)
+ if LspQfId == 0
+ setbufvar(bnr, 'LspQfId', getloclist(0, {id: 0}).id)
+ endif
+
+ return true
+enddef
+
+# Display the diagnostic messages from the LSP server for the current buffer
+# in a location list
+export def ShowAllDiags(): void
+ var bnr: number = bufnr()
+ if !DiagsUpdateLocList(bnr, true)
+ util.WarnMsg($'No diagnostic messages found for {@%}')
+ return
+ endif
+
+ var save_winid = win_getid()
+ # make the diagnostics error list the active one and open it
+ var LspQfId: number = bnr->getbufvar('LspQfId', 0)
+ var LspQfNr: number = getloclist(0, {id: LspQfId, nr: 0}).nr
+ exe $':{LspQfNr} lhistory'
+ :lopen
+ if !opt.lspOptions.keepFocusInDiags
+ save_winid->win_gotoid()
+ endif
+enddef
+
+# Display the message of "diag" in a popup window right below the position in
+# the diagnostic message.
+def ShowDiagInPopup(diag: dict<any>)
+ var d_start = diag.range.start
+ var dlnum = d_start.line + 1
+ var ltext = dlnum->getline()
+ var dlcol = ltext->byteidxcomp(d_start.character) + 1
+
+ var lastline = line('$')
+ if dlnum > lastline
+ # The line number is outside the last line in the file.
+ dlnum = lastline
+ endif
+ if dlcol < 1
+ # The column is outside the last character in line.
+ dlcol = ltext->len() + 1
+ endif
+ var d = screenpos(0, dlnum, dlcol)
+ if d->empty()
+ # If the diag position cannot be converted to Vim lnum/col, then use
+ # the current cursor position
+ d = {row: line('.'), col: col('.')}
+ endif
+
+ # Display a popup right below the diagnostics position
+ var msg = diag.message->split("\n")
+ var msglen = msg->reduce((acc, val) => max([acc, val->strcharlen()]), 0)
+
+ var ppopts = {}
+ ppopts.pos = 'topleft'
+ ppopts.line = d.row + 1
+ ppopts.moved = 'any'
+
+ if msglen > &columns
+ ppopts.wrap = true
+ ppopts.col = 1
+ else
+ ppopts.wrap = false
+ ppopts.col = d.col
+ endif
+
+ popup_create(msg, ppopts)
+enddef
+
+# Display the "diag" message in a popup or in the status message area
+def DisplayDiag(diag: dict<any>)
+ if opt.lspOptions.showDiagInPopup
+ # Display the diagnostic message in a popup window.
+ ShowDiagInPopup(diag)
+ else
+ # Display the diagnostic message in the status message area
+ :echo diag.message
+ endif
+enddef
+
+# Show the diagnostic message for the current line
+export def ShowCurrentDiag(atPos: bool)
+ var bnr: number = bufnr()
+ var lnum: number = line('.')
+ var col: number = charcol('.')
+ var diag: dict<any> = GetDiagByPos(bnr, lnum, col, atPos)
+ if diag->empty()
+ util.WarnMsg($'No diagnostic messages found for current {atPos ? "position" : "line"}')
+ else
+ DisplayDiag(diag)
+ endif
+enddef
+
+# Show the diagnostic message for the current line without linebreak
+def ShowCurrentDiagInStatusLine()
+ var bnr: number = bufnr()
+ var lnum: number = line('.')
+ var col: number = charcol('.')
+ var diag: dict<any> = GetDiagByPos(bnr, lnum, col)
+ if !diag->empty()
+ # 15 is a enough length not to cause line break
+ var max_width = &columns - 15
+ var code = ''
+ if diag->has_key('code')
+ code = $'[{diag.code}] '
+ endif
+ var msgNoLineBreak = code ..
+ diag.message->substitute("\n", ' ', '')->substitute("\\n", ' ', '')
+ :echo msgNoLineBreak[ : max_width]
+ else
+ # clear the previous message
+ :echo ''
+ endif
+enddef
+
+# Get the diagnostic from the LSP server for a particular line and character
+# offset in a file
+export def GetDiagByPos(bnr: number, lnum: number, col: number,
+ atPos: bool = false): dict<any>
+ var diags_in_line = GetDiagsByLine(bnr, lnum)
+
+ for diag in diags_in_line
+ var r = diag.range
+ var startCharIdx = util.GetCharIdxWithoutCompChar(bnr, r.start)
+ var endCharIdx = util.GetCharIdxWithoutCompChar(bnr, r.end)
+ if atPos
+ if col >= startCharIdx + 1 && col < endCharIdx + 1
+ return diag
+ endif
+ elseif col <= startCharIdx + 1
+ return diag
+ endif
+ endfor
+
+ # No diagnostic to the right of the position, return the last one instead
+ if !atPos && diags_in_line->len() > 0
+ return diags_in_line[-1]
+ endif
+
+ return {}
+enddef
+
+# Get all diagnostics from the LSP server for a particular line in a file
+export def GetDiagsByLine(bnr: number, lnum: number, lspserver: dict<any> = null_dict): list<dict<any>>
+ if !diagsMap->has_key(bnr)
+ return []
+ endif
+
+ var diags: list<dict<any>> = []
+
+ var serverDiagsByLnum = diagsMap[bnr].serverDiagnosticsByLnum
+
+ if lspserver == null_dict
+ for diagsByLnum in serverDiagsByLnum->values()
+ if diagsByLnum->has_key(lnum)
+ diags->extend(diagsByLnum[lnum])
+ endif
+ endfor
+ else
+ if !serverDiagsByLnum->has_key(lspserver.id)
+ return []
+ endif
+ if serverDiagsByLnum[lspserver.id]->has_key(lnum)
+ diags = serverDiagsByLnum[lspserver.id][lnum]
+ endif
+ endif
+
+ return diags->sort((a, b) => {
+ return a.range.start.character - b.range.start.character
+ })
+enddef
+
+# Utility function to do the actual jump
+def JumpDiag(diag: dict<any>)
+ var startPos: dict<number> = diag.range.start
+ setcursorcharpos(startPos.line + 1,
+ util.GetCharIdxWithoutCompChar(bufnr(), startPos) + 1)
+ :normal! zv
+ if !opt.lspOptions.showDiagWithVirtualText
+ :redraw
+ DisplayDiag(diag)
+ endif
+enddef
+
+# jump to the next/previous/first diagnostic message in the current buffer
+export def LspDiagsJump(which: string, a_count: number = 0): void
+ var fname: string = expand('%:p')
+ if fname->empty()
+ return
+ endif
+ var bnr: number = bufnr()
+
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ util.WarnMsg($'No diagnostic messages found for {fname}')
+ return
+ endif
+
+ var diags = diagsMap[bnr].sortedDiagnostics
+
+ if which == 'first'
+ JumpDiag(diags[0])
+ return
+ endif
+
+ if which == 'last'
+ JumpDiag(diags[-1])
+ return
+ endif
+
+ # Find the entry just before the current line (binary search)
+ var count = a_count > 1 ? a_count : 1
+ var curlnum: number = line('.')
+ var curcol: number = charcol('.')
+ for diag in (which == 'next' || which == 'nextWrap' || which == 'here') ?
+ diags : diags->copy()->reverse()
+ var d_start = diag.range.start
+ var lnum = d_start.line + 1
+ var col = util.GetCharIdxWithoutCompChar(bnr, d_start) + 1
+ if ((which == 'next' || which == 'nextWrap') && (lnum > curlnum || lnum == curlnum && col > curcol))
+ || ((which == 'prev' || which == 'prevWrap') && (lnum < curlnum || lnum == curlnum
+ && col < curcol))
+ || (which == 'here' && (lnum == curlnum && col >= curcol))
+
+ # Skip over as many diags as "count" dictates
+ count = count - 1
+ if count > 0
+ continue
+ endif
+
+ JumpDiag(diag)
+ return
+ endif
+ endfor
+
+ # If [count] exceeded the remaining diags
+ if ((which == 'next' || which == 'nextWrap') && a_count > 1 && a_count != count)
+ JumpDiag(diags[-1])
+ return
+ endif
+
+ # If [count] exceeded the previous diags
+ if ((which == 'prev' || which == 'prevWrap') && a_count > 1 && a_count != count)
+ JumpDiag(diags[0])
+ return
+ endif
+
+ if which == 'nextWrap' || which == 'prevWrap'
+ JumpDiag(diags[which == 'nextWrap' ? 0 : -1])
+ return
+ endif
+
+ if which == 'here'
+ util.WarnMsg('No more diagnostics found on this line')
+ else
+ util.WarnMsg('No more diagnostics found')
+ endif
+enddef
+
+# Return the sorted diagnostics for buffer "bnr". Default is the current
+# buffer. A copy of the diagnostics is returned so that the caller can modify
+# the diagnostics.
+export def GetDiagsForBuf(bnr: number = bufnr()): list<dict<any>>
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ return []
+ endif
+
+ return diagsMap[bnr].sortedDiagnostics->deepcopy()
+enddef
+
+# Return the diagnostic text from the LSP server for the current mouse line to
+# display in a balloon
+def g:LspDiagExpr(): any
+ if !opt.lspOptions.showDiagInBalloon
+ return ''
+ endif
+
+ var diagsInfo: list<dict<any>> =
+ GetDiagsByLine(v:beval_bufnr, v:beval_lnum)
+ if diagsInfo->empty()
+ # No diagnostic for the current cursor location
+ return ''
+ endif
+ var diagFound: dict<any> = {}
+ for diag in diagsInfo
+ var r = diag.range
+ var startcol = util.GetLineByteFromPos(v:beval_bufnr, r.start) + 1
+ var endcol = util.GetLineByteFromPos(v:beval_bufnr, r.end) + 1
+ if v:beval_col >= startcol && v:beval_col < endcol
+ diagFound = diag
+ break
+ endif
+ endfor
+ if diagFound->empty()
+ # mouse is outside of the diagnostics range
+ return ''
+ endif
+
+ # return the found diagnostic
+ return diagFound.message->split("\n")
+enddef
+
+# Track the current diagnostics auto highlight enabled/disabled state. Used
+# when the "autoHighlightDiags" option value is changed.
+var save_autoHighlightDiags = opt.lspOptions.autoHighlightDiags
+var save_highlightDiagInline = opt.lspOptions.highlightDiagInline
+var save_showDiagWithSign = opt.lspOptions.showDiagWithSign
+var save_showDiagWithVirtualText = opt.lspOptions.showDiagWithVirtualText
+
+# Enable the LSP diagnostics highlighting
+export def DiagsHighlightEnable()
+ opt.lspOptions.autoHighlightDiags = true
+ save_autoHighlightDiags = true
+ for binfo in getbufinfo({bufloaded: true})
+ if diagsMap->has_key(binfo.bufnr)
+ DiagsRefresh(binfo.bufnr)
+ endif
+ endfor
+enddef
+
+# Disable the LSP diagnostics highlighting in all the buffers
+export def DiagsHighlightDisable()
+ # turn off all diags highlight
+ opt.lspOptions.autoHighlightDiags = false
+ save_autoHighlightDiags = false
+ for binfo in getbufinfo()
+ if diagsMap->has_key(binfo.bufnr)
+ RemoveDiagVisualsForBuffer(binfo.bufnr)
+ endif
+ endfor
+enddef
+
+# Some options are changed. If 'autoHighlightDiags' option is changed, then
+# either enable or disable diags auto highlight.
+export def LspDiagsOptionsChanged()
+ if save_autoHighlightDiags && !opt.lspOptions.autoHighlightDiags
+ DiagsHighlightDisable()
+ elseif !save_autoHighlightDiags && opt.lspOptions.autoHighlightDiags
+ DiagsHighlightEnable()
+ endif
+
+ if save_highlightDiagInline != opt.lspOptions.highlightDiagInline
+ || save_showDiagWithSign != opt.lspOptions.showDiagWithSign
+ || save_showDiagWithVirtualText != opt.lspOptions.showDiagWithVirtualText
+ save_highlightDiagInline = opt.lspOptions.highlightDiagInline
+ save_showDiagWithSign = opt.lspOptions.showDiagWithSign
+ save_showDiagWithVirtualText = opt.lspOptions.showDiagWithVirtualText
+ for binfo in getbufinfo({bufloaded: true})
+ if diagsMap->has_key(binfo.bufnr)
+ DiagsRefresh(binfo.bufnr, true)
+ endif
+ endfor
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim
new file mode 100644
index 0000000..dd597ed
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim
@@ -0,0 +1,305 @@
+vim9script
+
+# Handlers for messages from the LSP server
+# Refer to https://microsoft.github.io/language-server-protocol/specification
+# for the Language Server Protocol (LSP) specification.
+
+import './util.vim'
+import './diag.vim'
+import './textedit.vim'
+
+# Process various reply messages from the LSP server
+export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
+ util.ErrMsg($'Unsupported reply received from LSP server: {reply->string()} for request: {req->string()}')
+enddef
+
+# process a diagnostic notification message from the LSP server
+# Notification: textDocument/publishDiagnostics
+# Param: PublishDiagnosticsParams
+def ProcessDiagNotif(lspserver: dict<any>, reply: dict<any>): void
+ var params = reply.params
+ diag.DiagNotification(lspserver, params.uri, params.diagnostics)
+enddef
+
+# Convert LSP message type to a string
+def LspMsgTypeToString(lspMsgType: number): string
+ var msgStrMap: list<string> = ['', 'Error', 'Warning', 'Info', 'Log']
+ var mtype: string = 'Log'
+ if lspMsgType > 0 && lspMsgType < 5
+ mtype = msgStrMap[lspMsgType]
+ endif
+ return mtype
+enddef
+
+# process a show notification message from the LSP server
+# Notification: window/showMessage
+# Param: ShowMessageParams
+def ProcessShowMsgNotif(lspserver: dict<any>, reply: dict<any>)
+ var msgType = reply.params.type
+ if msgType >= 4
+ # ignore log messages from the LSP server (too chatty)
+ # TODO: Add a configuration to control the message level that will be
+ # displayed. Also store these messages and provide a command to display
+ # them.
+ return
+ endif
+ if msgType == 1
+ util.ErrMsg($'Lsp({lspserver.name}) {reply.params.message}')
+ elseif msgType == 2
+ util.WarnMsg($'Lsp({lspserver.name}) {reply.params.message}')
+ elseif msgType == 3
+ util.InfoMsg($'Lsp({lspserver.name}) {reply.params.message}')
+ endif
+enddef
+
+# process a log notification message from the LSP server
+# Notification: window/logMessage
+# Param: LogMessageParams
+def ProcessLogMsgNotif(lspserver: dict<any>, reply: dict<any>)
+ var params = reply.params
+ var mtype = LspMsgTypeToString(params.type)
+ lspserver.addMessage(mtype, params.message)
+enddef
+
+# process the log trace notification messages
+# Notification: $/logTrace
+# Param: LogTraceParams
+def ProcessLogTraceNotif(lspserver: dict<any>, reply: dict<any>)
+ lspserver.addMessage('trace', reply.params.message)
+enddef
+
+# process unsupported notification messages
+def ProcessUnsupportedNotif(lspserver: dict<any>, reply: dict<any>)
+ util.WarnMsg($'Unsupported notification message received from the LSP server ({lspserver.name}), message = {reply->string()}')
+enddef
+
+# Dict to process telemetry notification messages only once per filetype
+var telemetryProcessed: dict<bool> = {}
+# process unsupported notification messages only once
+def ProcessUnsupportedNotifOnce(lspserver: dict<any>, reply: dict<any>)
+ if !telemetryProcessed->get(&ft, false)
+ ProcessUnsupportedNotif(lspserver, reply)
+ telemetryProcessed->extend({[&ft]: true})
+ endif
+enddef
+
+# process notification messages from the LSP server
+export def ProcessNotif(lspserver: dict<any>, reply: dict<any>): void
+ var lsp_notif_handlers: dict<func> =
+ {
+ 'window/showMessage': ProcessShowMsgNotif,
+ 'window/logMessage': ProcessLogMsgNotif,
+ 'textDocument/publishDiagnostics': ProcessDiagNotif,
+ '$/logTrace': ProcessLogTraceNotif,
+ 'telemetry/event': ProcessUnsupportedNotifOnce,
+ }
+
+ # Explicitly ignored notification messages (many of them are specific to a
+ # particular language server)
+ var lsp_ignored_notif_handlers: list<string> =
+ [
+ '$/progress',
+ '$/status/report',
+ '$/status/show',
+ # PHP intelephense server sends the "indexingStarted" and
+ # "indexingEnded" notifications which is not in the LSP specification.
+ 'indexingStarted',
+ 'indexingEnded',
+ # Java language server sends the 'language/status' notification which is
+ # not in the LSP specification.
+ 'language/status',
+ # Typescript language server sends the '$/typescriptVersion'
+ # notification which is not in the LSP specification.
+ '$/typescriptVersion',
+ # Dart language server sends the '$/analyzerStatus' notification which
+ # is not in the LSP specification.
+ '$/analyzerStatus',
+ # pyright language server notifications
+ 'pyright/beginProgress',
+ 'pyright/reportProgress',
+ 'pyright/endProgress',
+ 'eslint/status',
+ 'taplo/didChangeSchemaAssociation',
+ 'sqlLanguageServer.finishSetup',
+ # ccls language server notifications
+ '$ccls/publishSkippedRanges',
+ '$ccls/publishSemanticHighlight',
+ # omnisharp language server notifications
+ 'o#/backgrounddiagnosticstatus',
+ 'o#/msbuildprojectdiagnostics',
+ 'o#/projectadded',
+ 'o#/projectchanged',
+ 'o#/projectconfiguration',
+ 'o#/projectdiagnosticstatus',
+ 'o#/unresolveddependencies',
+ '@/tailwindCSS/projectInitialized'
+ ]
+
+ if lsp_notif_handlers->has_key(reply.method)
+ lsp_notif_handlers[reply.method](lspserver, reply)
+ elseif lspserver.customNotificationHandlers->has_key(reply.method)
+ lspserver.customNotificationHandlers[reply.method](lspserver, reply)
+ elseif lsp_ignored_notif_handlers->index(reply.method) == -1
+ ProcessUnsupportedNotif(lspserver, reply)
+ endif
+enddef
+
+# process the workspace/applyEdit LSP server request
+# Request: "workspace/applyEdit"
+# Param: ApplyWorkspaceEditParams
+def ProcessApplyEditReq(lspserver: dict<any>, request: dict<any>)
+ # interface ApplyWorkspaceEditParams
+ if !request->has_key('params')
+ return
+ endif
+ var workspaceEditParams: dict<any> = request.params
+ if workspaceEditParams->has_key('label')
+ util.InfoMsg($'Workspace edit {workspaceEditParams.label}')
+ endif
+ textedit.ApplyWorkspaceEdit(workspaceEditParams.edit)
+ # TODO: Need to return the proper result of the edit operation
+ lspserver.sendResponse(request, {applied: true}, {})
+enddef
+
+# process the workspace/workspaceFolders LSP server request
+# Request: "workspace/workspaceFolders"
+# Param: none
+def ProcessWorkspaceFoldersReq(lspserver: dict<any>, request: dict<any>)
+ if !lspserver->has_key('workspaceFolders')
+ lspserver.sendResponse(request, null, {})
+ return
+ endif
+ if lspserver.workspaceFolders->empty()
+ lspserver.sendResponse(request, [], {})
+ else
+ lspserver.sendResponse(request,
+ \ lspserver.workspaceFolders->copy()->map('{name: v:val->fnamemodify(":t"), uri: util.LspFileToUri(v:val)}'),
+ \ {})
+ endif
+enddef
+
+# process the workspace/configuration LSP server request
+# Request: "workspace/configuration"
+# Param: ConfigurationParams
+def ProcessWorkspaceConfiguration(lspserver: dict<any>, request: dict<any>)
+ var items = request.params.items
+ var response = items->map((_, item) => lspserver.workspaceConfigGet(item))
+
+ # Server expect null value if no config is given
+ if response->type() == v:t_list && response->len() == 1
+ && response[0]->type() == v:t_dict
+ && response[0] == null_dict
+ response[0] = null
+ endif
+
+ lspserver.sendResponse(request, response, {})
+enddef
+
+# process the window/workDoneProgress/create LSP server request
+# Request: "window/workDoneProgress/create"
+# Param: none
+def ProcessWorkDoneProgressCreate(lspserver: dict<any>, request: dict<any>)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process the window/showMessageRequest LSP server request
+# Request: "window/showMessageRequest"
+# Param: ShowMessageRequestParams
+def ProcessShowMessageRequest(lspserver: dict<any>, request: dict<any>)
+ # TODO: for now 'showMessageRequest' handled same like 'showMessage'
+ # regardless 'actions'
+ ProcessShowMsgNotif(lspserver, request)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process the client/registerCapability LSP server request
+# Request: "client/registerCapability"
+# Param: RegistrationParams
+def ProcessClientRegisterCap(lspserver: dict<any>, request: dict<any>)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process the client/unregisterCapability LSP server request
+# Request: "client/unregisterCapability"
+# Param: UnregistrationParams
+def ProcessClientUnregisterCap(lspserver: dict<any>, request: dict<any>)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process a request message from the server
+export def ProcessRequest(lspserver: dict<any>, request: dict<any>)
+ var lspRequestHandlers: dict<func> =
+ {
+ 'client/registerCapability': ProcessClientRegisterCap,
+ 'client/unregisterCapability': ProcessClientUnregisterCap,
+ 'window/workDoneProgress/create': ProcessWorkDoneProgressCreate,
+ 'window/showMessageRequest': ProcessShowMessageRequest,
+ 'workspace/applyEdit': ProcessApplyEditReq,
+ 'workspace/configuration': ProcessWorkspaceConfiguration,
+ 'workspace/workspaceFolders': ProcessWorkspaceFoldersReq
+ # TODO: Handle the following requests from the server:
+ # workspace/codeLens/refresh
+ # workspace/diagnostic/refresh
+ # workspace/inlayHint/refresh
+ # workspace/inlineValue/refresh
+ # workspace/semanticTokens/refresh
+ }
+
+ # Explicitly ignored requests
+ var lspIgnoredRequestHandlers: list<string> =
+ [
+ # Eclipse java language server sends the
+ # 'workspace/executeClientCommand' request (to reload bundles) which is
+ # not in the LSP specification.
+ 'workspace/executeClientCommand',
+ ]
+
+ if lspRequestHandlers->has_key(request.method)
+ lspRequestHandlers[request.method](lspserver, request)
+ elseif lspserver.customRequestHandlers->has_key(request.method)
+ lspserver.customRequestHandlers[request.method](lspserver, request)
+ elseif lspIgnoredRequestHandlers->index(request.method) == -1
+ util.ErrMsg($'Unsupported request message received from the LSP server ({lspserver.name}), message = {request->string()}')
+ endif
+enddef
+
+# process one or more LSP server messages
+export def ProcessMessages(lspserver: dict<any>): void
+ var idx: number
+ var len: number
+ var content: string
+ var msg: dict<any>
+ var req: dict<any>
+
+ msg = lspserver.data
+ if msg->has_key('result') || msg->has_key('error')
+ # response message from the server
+ req = lspserver.requests->get(msg.id->string(), {})
+ if !req->empty()
+ # Remove the corresponding stored request message
+ lspserver.requests->remove(msg.id->string())
+
+ if msg->has_key('result')
+ lspserver.processReply(req, msg)
+ else
+ # request failed
+ var emsg: string = msg.error.message
+ emsg ..= $', code = {msg.error.code}'
+ if msg.error->has_key('data')
+ emsg ..= $', data = {msg.error.data->string()}'
+ endif
+ util.ErrMsg($'request {req.method} failed ({emsg})')
+ endif
+ endif
+ elseif msg->has_key('id') && msg->has_key('method')
+ # request message from the server
+ lspserver.processRequest(msg)
+ elseif msg->has_key('method')
+ # notification message from the server
+ lspserver.processNotif(msg)
+ else
+ util.ErrMsg($'Unsupported message ({msg->string()})')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim
new file mode 100644
index 0000000..41e7f11
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim
@@ -0,0 +1,137 @@
+vim9script
+
+# Functions related to displaying hover symbol information.
+
+import './util.vim'
+import './options.vim' as opt
+
+# Util used to compute the hoverText from textDocument/hover reply
+def GetHoverText(lspserver: dict<any>, hoverResult: any): list<any>
+ if hoverResult->empty()
+ return ['', '']
+ endif
+
+ # MarkupContent
+ if hoverResult.contents->type() == v:t_dict
+ && hoverResult.contents->has_key('kind')
+ if hoverResult.contents.kind == 'plaintext'
+ return [hoverResult.contents.value->split("\n"), 'text']
+ endif
+
+ if hoverResult.contents.kind == 'markdown'
+ return [hoverResult.contents.value->split("\n"), 'lspgfm']
+ endif
+
+ lspserver.errorLog(
+ $'{strftime("%m/%d/%y %T")}: Unsupported hover contents kind ({hoverResult.contents.kind})'
+ )
+ return ['', '']
+ endif
+
+ # MarkedString
+ if hoverResult.contents->type() == v:t_dict
+ && hoverResult.contents->has_key('value')
+ return [
+ [$'``` {hoverResult.contents.language}']
+ + hoverResult.contents.value->split("\n")
+ + ['```'],
+ 'lspgfm'
+ ]
+ endif
+
+ # MarkedString
+ if hoverResult.contents->type() == v:t_string
+ return [hoverResult.contents->split("\n"), 'lspgfm']
+ endif
+
+ # interface MarkedString[]
+ if hoverResult.contents->type() == v:t_list
+ var hoverText: list<string> = []
+ for e in hoverResult.contents
+ if !hoverText->empty()
+ hoverText->extend(['- - -'])
+ endif
+
+ if e->type() == v:t_string
+ hoverText->extend(e->split("\n"))
+ else
+ hoverText->extend([$'``` {e.language}'])
+ hoverText->extend(e.value->split("\n"))
+ hoverText->extend(['```'])
+ endif
+ endfor
+
+ return [hoverText, 'lspgfm']
+ endif
+
+ lspserver.errorLog(
+ $'{strftime("%m/%d/%y %T")}: Unsupported hover reply ({hoverResult})'
+ )
+ return ['', '']
+enddef
+
+# Key filter function for the hover popup window.
+# Only keys to scroll the popup window are supported.
+def HoverWinFilterKey(hoverWin: number, key: string): bool
+ var keyHandled = false
+
+ if key == "\<C-E>"
+ || key == "\<C-D>"
+ || key == "\<C-F>"
+ || key == "\<PageDown>"
+ || key == "\<C-Y>"
+ || key == "\<C-U>"
+ || key == "\<C-B>"
+ || key == "\<PageUp>"
+ || key == "\<C-Home>"
+ || key == "\<C-End>"
+ # scroll the hover popup window
+ win_execute(hoverWin, $'normal! {key}')
+ keyHandled = true
+ endif
+
+ if key == "\<Esc>"
+ hoverWin->popup_close()
+ keyHandled = true
+ endif
+
+ return keyHandled
+enddef
+
+# process the 'textDocument/hover' reply from the LSP server
+# Result: Hover | null
+export def HoverReply(lspserver: dict<any>, hoverResult: any, cmdmods: string): void
+ var [hoverText, hoverKind] = GetHoverText(lspserver, hoverResult)
+
+ # Nothing to show
+ if hoverText->empty()
+ if cmdmods !~ 'silent'
+ util.WarnMsg($'No documentation found for current keyword')
+ endif
+ return
+ endif
+
+ if opt.lspOptions.hoverInPreview
+ execute $':silent! {cmdmods} pedit LspHover'
+ :wincmd P
+ :setlocal buftype=nofile
+ :setlocal bufhidden=delete
+ bufnr()->deletebufline(1, '$')
+ hoverText->append(0)
+ [1, 1]->cursor()
+ exe $'setlocal ft={hoverKind}'
+ :wincmd p
+ else
+ popup_clear()
+ var winid = hoverText->popup_atcursor({moved: 'any',
+ close: 'click',
+ fixed: true,
+ maxwidth: 80,
+ border: [0, 1, 0, 1],
+ borderchars: [' '],
+ filter: HoverWinFilterKey})
+ win_execute(winid, $'setlocal ft={hoverKind}')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim
new file mode 100644
index 0000000..a793699
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim
@@ -0,0 +1,235 @@
+vim9script
+
+# Functions for dealing with inlay hints
+
+import './util.vim'
+import './buffer.vim' as buf
+import './options.vim' as opt
+
+# Initialize the highlight group and the text property type used for
+# inlay hints.
+export def InitOnce()
+ hlset([
+ {name: 'LspInlayHintsType', default: true, linksto: 'Label'},
+ {name: 'LspInlayHintsParam', default: true, linksto: 'Conceal'}
+ ])
+ prop_type_add('LspInlayHintsType', {highlight: 'LspInlayHintsType'})
+ prop_type_add('LspInlayHintsParam', {highlight: 'LspInlayHintsParam'})
+
+ autocmd_add([{group: 'LspCmds',
+ event: 'User',
+ pattern: 'LspOptionsChanged',
+ cmd: 'LspInlayHintsOptionsChanged()'}])
+enddef
+
+# Clear all the inlay hints text properties in the current buffer
+def InlayHintsClear(bnr: number)
+ prop_remove({type: 'LspInlayHintsType', bufnr: bnr, all: true})
+ prop_remove({type: 'LspInlayHintsParam', bufnr: bnr, all: true})
+enddef
+
+# LSP inlay hints reply message handler
+export def InlayHintsReply(lspserver: dict<any>, bnr: number, inlayHints: any)
+ if inlayHints->empty()
+ return
+ endif
+
+ InlayHintsClear(bnr)
+
+ if mode() != 'n'
+ # Update inlay hints only in normal mode
+ return
+ endif
+
+ for hint in inlayHints
+ var label = ''
+ if hint.label->type() == v:t_list
+ label = hint.label->copy()->map((_, v) => v.value)->join('')
+ else
+ label = hint.label
+ endif
+
+ # add a space before or after the label
+ var padLeft: bool = hint->get('paddingLeft', false)
+ var padRight: bool = hint->get('paddingRight', false)
+ if padLeft
+ label = $' {label}'
+ endif
+ if padRight
+ label = $'{label} '
+ endif
+
+ var kind = hint->has_key('kind') ? hint.kind->string() : '1'
+ try
+ lspserver.decodePosition(bnr, hint.position)
+ var byteIdx = util.GetLineByteFromPos(bnr, hint.position)
+ if kind == "'type'" || kind == '1'
+ prop_add(hint.position.line + 1, byteIdx + 1,
+ {type: 'LspInlayHintsType', text: label, bufnr: bnr})
+ elseif kind == "'parameter'" || kind == '2'
+ prop_add(hint.position.line + 1, byteIdx + 1,
+ {type: 'LspInlayHintsParam', text: label, bufnr: bnr})
+ endif
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ # Inlay hints replies arrive asynchronously and the document might have
+ # been modified in the mean time. As the reply is stale, ignore invalid
+ # line number and column number errors.
+ endtry
+ endfor
+enddef
+
+# Timer callback to display the inlay hints.
+def InlayHintsTimerCb(lspserver: dict<any>, bnr: number, timerid: number)
+ lspserver.inlayHintsShow(bnr)
+ setbufvar(bnr, 'LspInlayHintsNeedsUpdate', false)
+enddef
+
+# Update all the inlay hints. A timer is used to throttle the updates.
+def LspInlayHintsUpdate(bnr: number)
+ if !bnr->getbufvar('LspInlayHintsNeedsUpdate', true)
+ return
+ endif
+
+ var timerid = bnr->getbufvar('LspInlayHintsTimer', -1)
+ if timerid != -1
+ timerid->timer_stop()
+ setbufvar(bnr, 'LspInlayHintsTimer', -1)
+ endif
+
+ var lspserver: dict<any> = buf.BufLspServerGet(bnr, 'inlayHint')
+ if lspserver->empty()
+ return
+ endif
+
+ if get(g:, 'LSPTest')
+ # When running tests, update the inlay hints immediately
+ InlayHintsTimerCb(lspserver, bnr, -1)
+ else
+ timerid = timer_start(300, function('InlayHintsTimerCb', [lspserver, bnr]))
+ setbufvar(bnr, 'LspInlayHintsTimer', timerid)
+ endif
+enddef
+
+# Text is modified. Need to update the inlay hints.
+def LspInlayHintsChanged(bnr: number)
+ setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true)
+enddef
+
+# Trigger an update of the inlay hints in the current buffer.
+export def LspInlayHintsUpdateNow(bnr: number)
+ setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true)
+ LspInlayHintsUpdate(bnr)
+enddef
+
+# Stop updating the inlay hints.
+def LspInlayHintsUpdateStop(bnr: number)
+ var timerid = bnr->getbufvar('LspInlayHintsTimer', -1)
+ if timerid != -1
+ timerid->timer_stop()
+ setbufvar(bnr, 'LspInlayHintsTimer', -1)
+ endif
+enddef
+
+# Do buffer-local initialization for displaying inlay hints
+export def BufferInit(lspserver: dict<any>, bnr: number)
+ if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
+ # no support for inlay hints
+ return
+ endif
+
+ # Inlays hints are disabled
+ if !opt.lspOptions.showInlayHints
+ || !lspserver.featureEnabled('inlayHint')
+ return
+ endif
+
+ var acmds: list<dict<any>> = []
+
+ # Update the inlay hints (if needed) when the cursor is not moved for some
+ # time.
+ acmds->add({bufnr: bnr,
+ event: ['CursorHold'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsUpdate({bnr})'})
+ # After the text in the current buffer is modified, the inlay hints need to
+ # be updated.
+ acmds->add({bufnr: bnr,
+ event: ['TextChanged'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsChanged({bnr})'})
+ # Editing a file should trigger an inlay hint update.
+ acmds->add({bufnr: bnr,
+ event: ['BufReadPost'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsUpdateNow({bnr})'})
+ # Inlay hints need not be updated if a buffer is no longer active.
+ acmds->add({bufnr: bnr,
+ event: ['BufLeave'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsUpdateStop({bnr})'})
+
+ # Inlay hints maybe a bit delayed if it was a sync init lsp server.
+ if lspserver.syncInit
+ acmds->add({bufnr: bnr,
+ event: ['User'],
+ group: 'LspAttached',
+ cmd: $'LspInlayHintsUpdateNow({bnr})'})
+ endif
+
+ autocmd_add(acmds)
+enddef
+
+# Track the current inlay hints enabled/disabled state. Used when the
+# "showInlayHints" option value is changed.
+var save_showInlayHints = opt.lspOptions.showInlayHints
+
+# Enable inlay hints. For all the buffers with an attached language server
+# that supports inlay hints, refresh the inlay hints.
+export def InlayHintsEnable()
+ opt.lspOptions.showInlayHints = true
+ for binfo in getbufinfo()
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(binfo.bufnr)
+ if lspservers->empty()
+ continue
+ endif
+ for lspserver in lspservers
+ if !lspserver.ready
+ || !lspserver.featureEnabled('inlayHint')
+ || (!lspserver.isInlayHintProvider &&
+ !lspserver.isClangdInlayHintsProvider)
+ continue
+ endif
+ BufferInit(lspserver, binfo.bufnr)
+ LspInlayHintsUpdateNow(binfo.bufnr)
+ endfor
+ endfor
+ save_showInlayHints = true
+enddef
+
+# Disable inlay hints for the current Vim session. Clear the inlay hints in
+# all the buffers.
+export def InlayHintsDisable()
+ opt.lspOptions.showInlayHints = false
+ for binfo in getbufinfo()
+ var lspserver: dict<any> = buf.BufLspServerGet(binfo.bufnr, 'inlayHint')
+ if lspserver->empty()
+ continue
+ endif
+ LspInlayHintsUpdateStop(binfo.bufnr)
+ :silent! autocmd_delete([{bufnr: binfo.bufnr, group: 'LspInlayHints'}])
+ InlayHintsClear(binfo.bufnr)
+ endfor
+ save_showInlayHints = false
+enddef
+
+# Some options are changed. If 'showInlayHints' option is changed, then
+# either enable or disable inlay hints.
+export def LspInlayHintsOptionsChanged()
+ if save_showInlayHints && !opt.lspOptions.showInlayHints
+ InlayHintsDisable()
+ elseif !save_showInlayHints && opt.lspOptions.showInlayHints
+ InlayHintsEnable()
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim
new file mode 100644
index 0000000..507eb00
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim
@@ -0,0 +1,1304 @@
+vim9script
+
+# Vim9 LSP client
+#
+# The functions called by plugin/lsp.vim are in this file.
+
+# Needs Vim 9.0 and higher
+if v:version < 900
+ finish
+endif
+
+import './options.vim' as opt
+import './lspserver.vim' as lserver
+import './util.vim'
+import './buffer.vim' as buf
+import './completion.vim'
+import './textedit.vim'
+import './diag.vim'
+import './symbol.vim'
+import './outline.vim'
+import './signature.vim'
+import './codeaction.vim'
+import './inlayhints.vim'
+import './semantichighlight.vim'
+
+# LSP server information
+var LSPServers: list<dict<any>> = []
+
+# filetype to LSP server map
+var ftypeServerMap: dict<list<dict<any>>> = {}
+
+var lspInitializedOnce = false
+
+def LspInitOnce()
+ hlset([
+ {name: 'LspTextRef', default: true, linksto: 'Search'},
+ {name: 'LspReadRef', default: true, linksto: 'DiffChange'},
+ {name: 'LspWriteRef', default: true, linksto: 'DiffDelete'}
+ ])
+
+ var override = &cursorline
+ && &cursorlineopt =~ '\<line\>\|\<screenline\>\|\<both\>'
+
+ prop_type_add('LspTextRef', {highlight: 'LspTextRef', override: override})
+ prop_type_add('LspReadRef', {highlight: 'LspReadRef', override: override})
+ prop_type_add('LspWriteRef', {highlight: 'LspWriteRef', override: override})
+
+ diag.InitOnce()
+ inlayhints.InitOnce()
+ signature.InitOnce()
+ symbol.InitOnce()
+ semantichighlight.InitOnce()
+
+ lspInitializedOnce = true
+enddef
+
+# Returns the LSP servers for the a specific filetype. Based on how well there
+# score, LSP servers with the same score are being returned.
+# Returns an empty list if the servers is not found.
+def LspGetServers(bnr: number, ftype: string): list<dict<any>>
+ if !ftypeServerMap->has_key(ftype)
+ return []
+ endif
+
+ var bufDir = bnr->bufname()->fnamemodify(':p:h')
+
+ return ftypeServerMap[ftype]->filter((key, lspserver) => {
+ # Don't run the server if no path is found
+ if !lspserver.runIfSearchFiles->empty()
+ var path = util.FindNearestRootDir(bufDir, lspserver.runIfSearchFiles)
+
+ if path->empty()
+ return false
+ endif
+ endif
+
+ # Don't run the server if a path is found
+ if !lspserver.runUnlessSearchFiles->empty()
+ var path = util.FindNearestRootDir(bufDir, lspserver.runUnlessSearchFiles)
+
+ if !path->empty()
+ return false
+ endif
+ endif
+
+ # Run the server
+ return true
+ })
+enddef
+
+# Add a LSP server for a filetype
+def LspAddServer(ftype: string, lspsrv: dict<any>)
+ var lspsrvlst = ftypeServerMap->has_key(ftype) ? ftypeServerMap[ftype] : []
+ lspsrvlst->add(lspsrv)
+ ftypeServerMap[ftype] = lspsrvlst
+enddef
+
+# Enable/disable the logging of the language server protocol messages
+def ServerDebug(arg: string)
+ if ['errors', 'messages', 'off', 'on']->index(arg) == -1
+ util.ErrMsg($'Unsupported argument "{arg}"')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ for lspserver in lspservers
+ if arg == 'on'
+ util.ClearTraceLogs(lspserver.logfile)
+ util.ClearTraceLogs(lspserver.errfile)
+ lspserver.debug = true
+ elseif arg == 'off'
+ lspserver.debug = false
+ elseif arg == 'messages'
+ util.ServerMessagesShow(lspserver.logfile)
+ else
+ util.ServerMessagesShow(lspserver.errfile)
+ endif
+ endfor
+enddef
+
+# Show information about all the LSP servers
+export def ShowAllServers()
+ var lines = []
+ # Add filetype to server mapping information
+ lines->add('Filetype Information')
+ lines->add('====================')
+ for [ftype, lspservers] in ftypeServerMap->items()
+ for lspserver in lspservers
+ lines->add($"Filetype: '{ftype}'")
+ lines->add($"Server Name: '{lspserver.name}'")
+ lines->add($"Server Path: '{lspserver.path}'")
+ lines->add($"Status: {lspserver.running ? 'Running' : 'Not running'}")
+ lines->add('')
+ endfor
+ endfor
+
+ # Add buffer to server mapping information
+ lines->add('Buffer Information')
+ lines->add('==================')
+ for bnr in range(1, bufnr('$'))
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ if !lspservers->empty()
+ lines->add($"Buffer: '{bufname(bnr)}'")
+ for lspserver in lspservers
+ lines->add($"Server Path: '{lspserver.path}'")
+ lines->add($"Status: {lspserver.running ? 'Running' : 'Not running'}")
+ endfor
+ lines->add('')
+ endif
+ endfor
+
+ var wid = bufwinid('Language-Servers')
+ if wid != -1
+ wid->win_gotoid()
+ :setlocal modifiable
+ :silent! :%d _
+ else
+ :new Language-Servers
+ :setlocal buftype=nofile
+ :setlocal bufhidden=wipe
+ :setlocal noswapfile
+ :setlocal nonumber nornu
+ :setlocal fdc=0 signcolumn=no
+ endif
+ setline(1, lines)
+ :setlocal nomodified
+ :setlocal nomodifiable
+enddef
+
+# Create a new window containing the buffer "bname" or if the window is
+# already present then jump to it.
+def OpenScratchWindow(bname: string)
+ var wid = bufwinid(bname)
+ if wid != -1
+ wid->win_gotoid()
+ :setlocal modifiable
+ :silent! :%d _
+ else
+ exe $':new {bname}'
+ :setlocal buftype=nofile
+ :setlocal bufhidden=wipe
+ :setlocal noswapfile
+ :setlocal nonumber nornu
+ :setlocal fdc=0 signcolumn=no
+ endif
+enddef
+
+# Show the status of the LSP server for the current buffer
+def ShowServer(arg: string)
+ if ['status', 'capabilities', 'initializeRequest', 'messages']->index(arg) == -1
+ util.ErrMsg($'Unsupported argument "{arg}"')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ var windowName: string = ''
+ var lines: list<string> = []
+ if arg->empty() || arg == 'status'
+ windowName = $'LangServer-Status'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ var msg = $"LSP server '{lspserver.name}' is "
+ if lspserver.running
+ msg ..= 'running'
+ else
+ msg ..= 'not running'
+ endif
+ lines->add(msg)
+ endfor
+ elseif arg == 'capabilities'
+ windowName = $'LangServer-Capabilities'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ lines->extend(lspserver.getCapabilities())
+ endfor
+ elseif arg == 'initializeRequest'
+ windowName = $'LangServer-InitializeRequest'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ lines->extend(lspserver.getInitializeRequest())
+ endfor
+ elseif arg == 'messages'
+ windowName = $'LangServer-Messages'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ lines->extend(lspserver.getMessages())
+ endfor
+ else
+ util.ErrMsg($'Unsupported argument "{arg}"')
+ return
+ endif
+
+ if lines->len() > 1
+ OpenScratchWindow(windowName)
+ setline(1, lines)
+ :setlocal nomodified
+ :setlocal nomodifiable
+ else
+ util.InfoMsg(lines[0])
+ endif
+enddef
+
+# Get LSP server running status for filetype "ftype"
+# Return true if running, or false if not found or not running
+export def ServerRunning(ftype: string): bool
+ if ftypeServerMap->has_key(ftype)
+ var lspservers = ftypeServerMap[ftype]
+ for lspserver in lspservers
+ if lspserver.running
+ return true
+ endif
+ endfor
+ endif
+
+ return false
+enddef
+
+# Go to a definition using "textDocument/definition" LSP request
+export def GotoDefinition(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('definition')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoDefinition(peek, cmdmods, count)
+enddef
+
+# Go to a declaration using "textDocument/declaration" LSP request
+export def GotoDeclaration(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('declaration')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoDeclaration(peek, cmdmods, count)
+enddef
+
+# Go to a type definition using "textDocument/typeDefinition" LSP request
+export def GotoTypedef(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('typeDefinition')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoTypeDef(peek, cmdmods, count)
+enddef
+
+# Go to a implementation using "textDocument/implementation" LSP request
+export def GotoImplementation(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('implementation')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoImplementation(peek, cmdmods, count)
+enddef
+
+# Switch source header using "textDocument/switchSourceHeader" LSP request
+# (Clangd specifc extension)
+export def SwitchSourceHeader()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked()
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.switchSourceHeader()
+enddef
+
+# A buffer is saved. Send the "textDocument/didSave" LSP notification
+def LspSavedFile(bnr: number)
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)->filter(
+ (key, lspsrv) => !lspsrv->empty() && lspsrv.running
+ )
+
+ if lspservers->empty()
+ return
+ endif
+
+ for lspserver in lspservers
+ lspserver.didSaveFile(bnr)
+ endfor
+enddef
+
+# Called after leaving insert mode. Used to process diag messages (if any)
+def LspLeftInsertMode(bnr: number)
+ var updatePending: bool = bnr->getbufvar('LspDiagsUpdatePending', false)
+ if !updatePending
+ return
+ endif
+ setbufvar(bnr, 'LspDiagsUpdatePending', false)
+
+ diag.ProcessNewDiags(bnr)
+enddef
+
+# Add buffer-local autocmds when attaching a LSP server to a buffer
+def AddBufLocalAutocmds(lspserver: dict<any>, bnr: number): void
+ var acmds: list<dict<any>> = []
+
+ # file saved notification handler
+ acmds->add({bufnr: bnr,
+ event: 'BufWritePost',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspSavedFile({bnr})'})
+
+ # Update the diagnostics when insert mode is stopped
+ acmds->add({bufnr: bnr,
+ event: 'InsertLeave',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspLeftInsertMode({bnr})'})
+
+ # Auto highlight all the occurrences of the current keyword
+ if opt.lspOptions.autoHighlight &&
+ lspserver.isDocumentHighlightProvider
+ acmds->add({bufnr: bnr,
+ event: 'CursorMoved',
+ group: 'LSPBufferAutocmds',
+ cmd: $'call LspDocHighlightClear({bnr}) | call LspDocHighlight({bnr}, "silent")'})
+ endif
+
+ autocmd_add(acmds)
+enddef
+
+# The LSP server with ID "lspserverId" is ready, initialize the LSP features
+# for buffer "bnr".
+def BufferInit(lspserverId: number, bnr: number): void
+ var lspserver = buf.BufLspServerGetById(bnr, lspserverId)
+ if lspserver->empty() || !lspserver.running
+ return
+ endif
+
+ var ftype: string = bnr->getbufvar('&filetype')
+ lspserver.textdocDidOpen(bnr, ftype)
+
+ # add a listener to track changes to this buffer
+ listener_add((_bnr: number, start: number, end: number, added: number, changes: list<dict<number>>) => {
+ lspserver.textdocDidChange(bnr, start, end, added, changes)
+ }, bnr)
+
+ AddBufLocalAutocmds(lspserver, bnr)
+
+ diag.BufferInit(lspserver, bnr)
+
+ var allServersReady = true
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ for lspsrv in lspservers
+ if !lspsrv.ready
+ allServersReady = false
+ break
+ endif
+ endfor
+
+ if allServersReady
+ for lspsrv in lspservers
+ # It's only possible to initialize the features when the server
+ # capabilities of all the registered language servers for this file type
+ # are known.
+ var completionServer = buf.BufLspServerGet(bnr, 'completion')
+ if !completionServer->empty() && lspsrv.id == completionServer.id
+ completion.BufferInit(lspsrv, bnr, ftype)
+ endif
+
+ var signatureServer = buf.BufLspServerGet(bnr, 'signatureHelp')
+ if !signatureServer->empty() && lspsrv.id == signatureServer.id
+ signature.BufferInit(lspsrv)
+ endif
+
+ var inlayHintServer = buf.BufLspServerGet(bnr, 'inlayHint')
+ if !inlayHintServer->empty() && lspsrv.id == inlayHintServer.id
+ inlayhints.BufferInit(lspsrv, bnr)
+ endif
+
+ var semanticServer = buf.BufLspServerGet(bnr, 'semanticTokens')
+ if !semanticServer->empty() && lspsrv.id == semanticServer.id
+ semantichighlight.BufferInit(lspserver, bnr)
+ endif
+ endfor
+
+ if exists('#User#LspAttached')
+ doautocmd <nomodeline> User LspAttached
+ endif
+ endif
+enddef
+
+# A new buffer is opened. If LSP is supported for this buffer, then add it
+export def AddFile(bnr: number): void
+ if buf.BufHasLspServer(bnr)
+ # LSP server for this buffer is already initialized and running
+ return
+ endif
+
+ # Skip remote files
+ if util.LspUriRemote(bnr->bufname()->fnamemodify(':p'))
+ return
+ endif
+
+ var ftype: string = bnr->getbufvar('&filetype')
+ if ftype->empty()
+ return
+ endif
+ var lspservers: list<dict<any>> = LspGetServers(bnr, ftype)
+ if lspservers->empty()
+ return
+ endif
+ for lspserver in lspservers
+ if !lspserver.running
+ if !lspInitializedOnce
+ LspInitOnce()
+ endif
+ lspserver.startServer(bnr)
+ endif
+ buf.BufLspServerSet(bnr, lspserver)
+
+ if lspserver.ready
+ BufferInit(lspserver.id, bnr)
+ else
+ augroup LSPBufferAutocmds
+ exe $'autocmd User LspServerReady_{lspserver.id} ++once BufferInit({lspserver.id}, {bnr})'
+ augroup END
+ endif
+ endfor
+enddef
+
+# Notify LSP server to remove a file
+export def RemoveFile(bnr: number): void
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ for lspserver in lspservers->copy()
+ if lspserver->empty()
+ continue
+ endif
+ if lspserver.running
+ lspserver.textdocDidClose(bnr)
+ endif
+ diag.DiagRemoveFile(bnr)
+ buf.BufLspServerRemove(bnr, lspserver)
+ endfor
+enddef
+
+# Buffer 'bnr' is loaded in a window, send the latest buffer contents to the
+# language servers.
+export def BufferLoadedInWin(bnr: number)
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ if lspservers->empty()
+ # No language servers for this buffer
+ return
+ endif
+ for lspserver in lspservers
+ if !lspserver->empty() && lspserver.ready
+ lspserver.textdocDidChange(bnr, 0, 0, 0, [])
+ endif
+ endfor
+ # Refresh the displayed diags visuals
+ if opt.lspOptions.autoHighlightDiags
+ diag.DiagsRefresh(bnr)
+ endif
+
+ completion.BufferLoadedInWin(bnr)
+
+ # Refresh the semantic highlights
+ if opt.lspOptions.semanticHighlight
+ var semanticServer = buf.BufLspServerGet(bnr, 'semanticTokens')
+ if !semanticServer->empty()
+ semanticServer.semanticHighlightUpdate(bnr)
+ endif
+ endif
+enddef
+
+# Stop all the LSP servers
+export def StopAllServers()
+ for lspserver in LSPServers
+ if lspserver.running
+ lspserver.stopServer()
+ endif
+ endfor
+enddef
+
+# Add all the buffers with 'filetype' set to "ftype" to the language server.
+def AddBuffersToLsp(ftype: string)
+ # Add all the buffers with the same file type as the current buffer
+ for binfo in getbufinfo({bufloaded: 1})
+ if binfo.bufnr->getbufvar('&filetype') == ftype
+ AddFile(binfo.bufnr)
+ endif
+ endfor
+enddef
+
+# Restart the LSP server for the current buffer
+def RestartServer()
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ # Remove all the buffers with the same file type as the current buffer
+ var ftype: string = &filetype
+ for binfo in getbufinfo()
+ if binfo.bufnr->getbufvar('&filetype') == ftype
+ RemoveFile(binfo.bufnr)
+ endif
+ endfor
+
+ for lspserver in lspservers
+ # Stop the server (if running)
+ if lspserver.running
+ lspserver.stopServer()
+ endif
+
+ # Start the server again
+ lspserver.startServer(bufnr())
+ endfor
+
+ AddBuffersToLsp(ftype)
+enddef
+
+# Add the LSP server for files with 'filetype' as "ftype".
+def AddServerForFiltype(lspserver: dict<any>, ftype: string, omnicompl: bool)
+ LspAddServer(ftype, lspserver)
+ completion.OmniComplSet(ftype, omnicompl)
+
+ # If a buffer of this file type is already present, then send it to the LSP
+ # server now.
+ AddBuffersToLsp(ftype)
+enddef
+
+# Register a LSP server for one or more file types
+export def AddServer(serverList: list<dict<any>>)
+ for server in serverList
+ if !server->has_key('filetype') || !server->has_key('path')
+ util.ErrMsg('LSP server information is missing filetype or path')
+ continue
+ endif
+ # Enable omni-completion by default
+ var omnicompl_def: bool = false
+ if opt.lspOptions.omniComplete == true
+ || (opt.lspOptions.omniComplete == null
+ && !opt.lspOptions.autoComplete)
+ omnicompl_def = true
+ endif
+ server.omnicompl = server->get('omnicompl', omnicompl_def)
+
+ if !server.path->executable()
+ if !opt.lspOptions.ignoreMissingServer
+ util.ErrMsg($'LSP server {server.path} is not found')
+ endif
+ continue
+ endif
+ if server->has_key('args')
+ if server.args->type() != v:t_list
+ util.ErrMsg($'Arguments for LSP server {server.args} is not a List')
+ continue
+ endif
+ else
+ server.args = []
+ endif
+
+ if !server->has_key('initializationOptions')
+ || server.initializationOptions->type() != v:t_dict
+ server.initializationOptions = {}
+ endif
+
+ if !server->has_key('forceOffsetEncoding')
+ || server.forceOffsetEncoding->type() != v:t_string
+ || (server.forceOffsetEncoding != 'utf-8'
+ && server.forceOffsetEncoding != 'utf-16'
+ && server.forceOffsetEncoding != 'utf-32')
+ server.forceOffsetEncoding = ''
+ endif
+
+ if !server->has_key('customNotificationHandlers')
+ || server.customNotificationHandlers->type() != v:t_dict
+ server.customNotificationHandlers = {}
+ endif
+
+ if server->has_key('processDiagHandler')
+ if server.processDiagHandler->type() != v:t_func
+ util.ErrMsg($'Setting of processDiagHandler {server.processDiagHandler} is not a Funcref nor lambda')
+ continue
+ endif
+ else
+ server.processDiagHandler = null_function
+ endif
+
+ if !server->has_key('customRequestHandlers')
+ || server.customRequestHandlers->type() != v:t_dict
+ server.customRequestHandlers = {}
+ endif
+
+ if !server->has_key('features') || server.features->type() != v:t_dict
+ server.features = {}
+ endif
+
+ if server.omnicompl->type() != v:t_bool
+ util.ErrMsg($'Setting of omnicompl {server.omnicompl} is not a Boolean')
+ continue
+ endif
+
+ if !server->has_key('syncInit')
+ server.syncInit = false
+ endif
+
+ if !server->has_key('name') || server.name->type() != v:t_string
+ || server.name->empty()
+ # Use the executable name (without the extension) as the language server
+ # name.
+ server.name = server.path->fnamemodify(':t:r')
+ endif
+
+ if !server->has_key('debug') || server.debug->type() != v:t_bool
+ server.debug = false
+ endif
+
+ if !server->has_key('traceLevel')
+ || server->type() != v:t_string
+ || (server.traceLevel != 'off' && server.traceLevel != 'debug'
+ && server.traceLevel != 'verbose')
+ server.traceLevel = 'off'
+ endif
+
+ if !server->has_key('workspaceConfig')
+ || server.workspaceConfig->type() != v:t_dict
+ server.workspaceConfig = {}
+ endif
+
+ if !server->has_key('rootSearch') || server.rootSearch->type() != v:t_list
+ server.rootSearch = []
+ endif
+
+ if !server->has_key('runIfSearch') ||
+ server.runIfSearch->type() != v:t_list
+ server.runIfSearch = []
+ endif
+
+ if !server->has_key('runUnlessSearch') ||
+ server.runUnlessSearch->type() != v:t_list
+ server.runUnlessSearch = []
+ endif
+
+ var lspserver: dict<any> = lserver.NewLspServer(server)
+
+ var ftypes = server.filetype
+ if ftypes->type() == v:t_string
+ AddServerForFiltype(lspserver, ftypes, server.omnicompl)
+ elseif ftypes->type() == v:t_list
+ for ftype in ftypes
+ AddServerForFiltype(lspserver, ftype, server.omnicompl)
+ endfor
+ else
+ util.ErrMsg($'Unsupported file type information "{ftypes->string()}" in LSP server registration')
+ continue
+ endif
+ endfor
+enddef
+
+# The LSP server is considered ready when the server capabilities are
+# received ("initialize" LSP reply message)
+export def ServerReady(): bool
+ var fname: string = @%
+ if fname->empty()
+ return false
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ return false
+ endif
+
+ for lspserver in lspservers
+ if !lspserver.ready
+ return false
+ endif
+ endfor
+
+ return true
+enddef
+
+# set the LSP server trace level for the current buffer
+# Params: SetTraceParams
+def ServerTraceSet(traceVal: string)
+ if ['off', 'messages', 'verbose']->index(traceVal) == -1
+ util.ErrMsg($'Unsupported argument "{traceVal}"')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ for lspserver in lspservers
+ lspserver.setTrace(traceVal)
+ endfor
+enddef
+
+# Display the diagnostic messages from the LSP server for the current buffer
+# in a quickfix list
+export def ShowDiagnostics(): void
+ diag.ShowAllDiags()
+enddef
+
+# Show the diagnostic message for the current line
+export def LspShowCurrentDiag(atPos: bool)
+ diag.ShowCurrentDiag(atPos)
+enddef
+
+# get the count of diagnostics in the current buffer
+export def ErrorCount(): dict<number>
+ var res = {Error: 0, Warn: 0, Info: 0, Hint: 0}
+ var fname: string = @%
+ if fname->empty()
+ return res
+ endif
+
+ return diag.DiagsGetErrorCount(bufnr())
+enddef
+
+# jump to the next/previous/first diagnostic message in the current buffer
+export def JumpToDiag(which: string, count: number = 0): void
+ diag.LspDiagsJump(which, count)
+enddef
+
+# Display the hover message from the LSP server for the current cursor
+# location
+export def Hover(cmdmods: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('hover')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.hover(cmdmods)
+enddef
+
+# Enable or disable inlay hints
+export def InlayHints(ctl: string)
+ if ctl == 'enable'
+ inlayhints.InlayHintsEnable()
+ elseif ctl == 'disable'
+ inlayhints.InlayHintsDisable()
+ else
+ util.ErrMsg($'LspInlayHints - Unsupported argument "{ctl}"')
+ endif
+enddef
+
+# Command-line completion for the ":LspInlayHints" command
+export def LspInlayHintsComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ var l = ['enable', 'disable']
+ return filter(l, (_, val) => val =~ $'^{arglead}')
+enddef
+
+# show symbol references
+export def ShowReferences(peek: bool)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('references')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.showReferences(peek)
+enddef
+
+# highlight all the places where a symbol is referenced
+def g:LspDocHighlight(bnr: number = bufnr(), cmdmods: string = '')
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.docHighlight(bnr, cmdmods)
+enddef
+
+# clear the symbol reference highlight
+def g:LspDocHighlightClear(bnr: number = bufnr())
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
+ if lspserver->empty()
+ return
+ endif
+
+ var propNames = ['LspTextRef', 'LspReadRef', 'LspWriteRef']
+ if has('patch-9.0.0233')
+ prop_remove({types: propNames, bufnr: bnr, all: true})
+ else
+ for propName in propNames
+ prop_remove({type: propName, bufnr: bnr, all: true})
+ endfor
+ endif
+enddef
+
+def g:LspRequestDocSymbols()
+ if outline.SkipOutlineRefresh()
+ return
+ endif
+
+ var fname: string = @%
+ if fname->empty()
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServer('documentSymbol')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ lspserver.getDocSymbols(fname, true)
+enddef
+
+# open a window and display all the symbols in a file (outline)
+export def Outline(cmdmods: string, winsize: number)
+ var fname: string = @%
+ if fname->empty()
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ outline.OpenOutlineWindow(cmdmods, winsize)
+ g:LspRequestDocSymbols()
+enddef
+
+# show all the symbols in a file in a popup menu
+export def ShowDocSymbols()
+ var fname: string = @%
+ if fname->empty()
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ lspserver.getDocSymbols(fname, false)
+enddef
+
+# Format the entire file
+export def TextDocFormat(range_args: number, line1: number, line2: number)
+ if !&modifiable
+ util.ErrMsg('Current file is not a modifiable file')
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
+ if lspserver->empty()
+ return
+ endif
+
+ var fname: string = @%
+ if range_args > 0
+ lspserver.textDocFormat(fname, true, line1, line2)
+ else
+ lspserver.textDocFormat(fname, false, 0, 0)
+ endif
+enddef
+
+# TODO: Add support for textDocument.onTypeFormatting?
+# Will this slow down Vim?
+
+# Display all the locations where the current symbol is called from.
+# Uses LSP "callHierarchy/incomingCalls" request
+export def IncomingCalls()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.incomingCalls(@%)
+enddef
+
+# Display all the symbols used by the current symbol.
+# Uses LSP "callHierarchy/outgoingCalls" request
+export def OutgoingCalls()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.outgoingCalls(@%)
+enddef
+
+# Display the type hierarchy for the current symbol. Direction is 0 for
+# sub types and 1 for super types.
+export def TypeHierarchy(direction: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('typeHierarchy')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.typeHierarchy(direction)
+enddef
+
+# Rename a symbol
+# Uses LSP "textDocument/rename" request
+export def Rename(a_newName: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('rename')
+ if lspserver->empty()
+ return
+ endif
+
+ var newName: string = a_newName
+ if newName->empty()
+ var sym: string = expand('<cword>')
+ newName = input($"Rename symbol '{sym}' to: ", sym)
+ if newName->empty()
+ return
+ endif
+
+ # clear the input prompt
+ :echo "\r"
+ endif
+
+ lspserver.renameSymbol(newName)
+enddef
+
+# Perform a code action
+# Uses LSP "textDocument/codeAction" request
+export def CodeAction(line1: number, line2: number, query: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('codeAction')
+ if lspserver->empty()
+ return
+ endif
+
+ var fname: string = @%
+ lspserver.codeAction(fname, line1, line2, query)
+enddef
+
+# Code lens
+# Uses LSP "textDocument/codeLens" request
+export def CodeLens()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('codeLens')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.codeLens(@%)
+enddef
+
+# Perform a workspace wide symbol lookup
+# Uses LSP "workspace/symbol" request
+export def SymbolSearch(queryArg: string, cmdmods: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('workspaceSymbol')
+ if lspserver->empty()
+ return
+ endif
+
+ var query: string = queryArg
+ if query->empty()
+ query = input('Lookup symbol: ', expand('<cword>'))
+ if query->empty()
+ return
+ endif
+ endif
+ :redraw!
+
+ lspserver.workspaceQuery(query, true, cmdmods)
+enddef
+
+# Display the list of workspace folders
+export def ListWorkspaceFolders()
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ for lspserver in lspservers
+ util.InfoMsg($'Workspace Folders: "{lspserver.name}" {lspserver.workspaceFolders->string()}')
+ endfor
+enddef
+
+# Add a workspace folder. Default is to use the current folder.
+export def AddWorkspaceFolder(dirArg: string)
+ var dirName: string = dirArg
+ if dirName->empty()
+ dirName = input('Add Workspace Folder: ', getcwd(), 'dir')
+ if dirName->empty()
+ return
+ endif
+ endif
+ :redraw!
+ if !dirName->isdirectory()
+ util.ErrMsg($'{dirName} is not a directory')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+
+ for lspserver in lspservers
+ lspserver.addWorkspaceFolder(dirName)
+ endfor
+enddef
+
+# Remove a workspace folder. Default is to use the current folder.
+export def RemoveWorkspaceFolder(dirArg: string)
+ var dirName: string = dirArg
+ if dirName->empty()
+ dirName = input('Remove Workspace Folder: ', getcwd(), 'dir')
+ if dirName->empty()
+ return
+ endif
+ endif
+ :redraw!
+ if !dirName->isdirectory()
+ util.ErrMsg($'{dirName} is not a directory')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ for lspserver in lspservers
+ lspserver.removeWorkspaceFolder(dirName)
+ endfor
+enddef
+
+# expand the previous selection or start a new selection
+export def SelectionExpand()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.selectionExpand()
+enddef
+
+# shrink the previous selection or start a new selection
+export def SelectionShrink()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.selectionShrink()
+enddef
+
+# fold the entire document
+export def FoldDocument()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('foldingRange')
+ if lspserver->empty()
+ return
+ endif
+
+ if &foldmethod != 'manual'
+ util.ErrMsg("Only works when 'foldmethod' is 'manual'")
+ return
+ endif
+
+ var fname: string = @%
+ lspserver.foldRange(fname)
+enddef
+
+# Enable diagnostic highlighting for all the buffers
+export def DiagHighlightEnable()
+ diag.DiagsHighlightEnable()
+enddef
+
+# Disable diagnostic highlighting for all the buffers
+export def DiagHighlightDisable()
+ diag.DiagsHighlightDisable()
+enddef
+
+# Function to use with the 'tagfunc' option.
+export def TagFunc(pat: string, flags: string, info: dict<any>): any
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('definition')
+ if lspserver->empty()
+ return v:null
+ endif
+
+ return lspserver.tagFunc(pat, flags, info)
+enddef
+
+# Function to use with the 'formatexpr' option.
+export def FormatExpr(): number
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
+ if lspserver->empty()
+ return 1
+ endif
+
+ lspserver.textDocFormat(@%, true, v:lnum, v:lnum + v:count - 1)
+ return 0
+enddef
+
+export def RegisterCmdHandler(cmd: string, Handler: func)
+ codeaction.RegisterCmdHandler(cmd, Handler)
+enddef
+
+# Command-line completion for the ":LspServer <cmd>" and ":LspDiag <cmd>" sub
+# commands
+def LspSubCmdComplete(cmds: list<string>, arglead: string, cmdline: string, cursorPos: number): list<string>
+ var wordBegin = cmdline->match('\s\+\zs\S', cursorPos)
+ if wordBegin == -1
+ return cmds
+ endif
+
+ # Make sure there are no additional sub-commands
+ var wordEnd = cmdline->stridx(' ', wordBegin)
+ if wordEnd == -1
+ return cmds->filter((_, val) => val =~ $'^{arglead}')
+ endif
+
+ return []
+enddef
+
+# Command-line completion for the ":LspDiag highlight" command
+def LspDiagHighlightComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['enable', 'disable'], arglead, cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspDiag" command
+export def LspDiagComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ var wordBegin = -1
+ var wordEnd = -1
+ var l = ['first', 'current', 'here', 'highlight', 'last', 'next', 'nextWrap',
+ 'prev', 'prevWrap', 'show']
+
+ # Skip the command name
+ var i = cmdline->stridx(' ', 0)
+ wordBegin = cmdline->match('\s\+\zs\S', i)
+ if wordBegin == -1
+ return l
+ endif
+
+ wordEnd = cmdline->stridx(' ', wordBegin)
+ if wordEnd == -1
+ return filter(l, (_, val) => val =~ $'^{arglead}')
+ endif
+
+ var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
+ if cmd == 'highlight'
+ return LspDiagHighlightComplete(arglead, cmdline, wordEnd)
+ endif
+
+ return []
+enddef
+
+# ":LspDiag" command handler
+export def LspDiagCmd(args: string, cmdCount: number, force: bool)
+ if args->stridx('highlight') == 0
+ if args[9] == ' '
+ var subcmd = args[10 : ]->trim()
+ if subcmd == 'enable'
+ diag.DiagsHighlightEnable()
+ elseif subcmd == 'disable'
+ diag.DiagsHighlightDisable()
+ else
+ util.ErrMsg($':LspDiag highlight - Unsupported argument "{subcmd}"')
+ endif
+ else
+ util.ErrMsg('Argument required for ":LspDiag highlight"')
+ endif
+ elseif args == 'first'
+ diag.LspDiagsJump('first', 0)
+ elseif args == 'current'
+ LspShowCurrentDiag(force)
+ elseif args == 'here'
+ diag.LspDiagsJump('here', 0)
+ elseif args == 'last'
+ diag.LspDiagsJump('last', 0)
+ elseif args == 'next'
+ diag.LspDiagsJump('next', cmdCount)
+ elseif args == 'nextWrap'
+ diag.LspDiagsJump('nextWrap', cmdCount)
+ elseif args == 'prev'
+ diag.LspDiagsJump('prev', cmdCount)
+ elseif args == 'prevWrap'
+ diag.LspDiagsJump('prevWrap', cmdCount)
+ elseif args == 'show'
+ ShowDiagnostics()
+ else
+ util.ErrMsg($':LspDiag - Unsupported argument "{args}"')
+ endif
+enddef
+
+# Command-line completion for the ":LspServer debug" command
+def LspServerDebugComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['errors', 'messages', 'off', 'on'], arglead,
+ cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer show" command
+def LspServerShowComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['capabilities', 'initializeRequest', 'messages',
+ 'status'], arglead, cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer trace" command
+def LspServerTraceComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['messages', 'off', 'verbose'], arglead, cmdline,
+ cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer" command
+export def LspServerComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ var wordBegin = -1
+ var wordEnd = -1
+ var l = ['debug', 'restart', 'show', 'trace']
+
+ # Skip the command name
+ var i = cmdline->stridx(' ', 0)
+ wordBegin = cmdline->match('\s\+\zs\S', i)
+ if wordBegin == -1
+ return l
+ endif
+
+ wordEnd = cmdline->stridx(' ', wordBegin)
+ if wordEnd == -1
+ return filter(l, (_, val) => val =~ $'^{arglead}')
+ endif
+
+ var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
+ if cmd == 'debug'
+ return LspServerDebugComplete(arglead, cmdline, wordEnd)
+ elseif cmd == 'restart'
+ elseif cmd == 'show'
+ return LspServerShowComplete(arglead, cmdline, wordEnd)
+ elseif cmd == 'trace'
+ return LspServerTraceComplete(arglead, cmdline, wordEnd)
+ endif
+
+ return []
+enddef
+
+# ":LspServer" command handler
+export def LspServerCmd(args: string)
+ if args->stridx('debug') == 0
+ if args[5] == ' '
+ var subcmd = args[6 : ]->trim()
+ ServerDebug(subcmd)
+ else
+ util.ErrMsg('Argument required for ":LspServer debug"')
+ endif
+ elseif args == 'restart'
+ RestartServer()
+ elseif args->stridx('show') == 0
+ if args[4] == ' '
+ var subcmd = args[5 : ]->trim()
+ ShowServer(subcmd)
+ else
+ util.ErrMsg('Argument required for ":LspServer show"')
+ endif
+ elseif args->stridx('trace') == 0
+ if args[5] == ' '
+ var subcmd = args[6 : ]->trim()
+ ServerTraceSet(subcmd)
+ else
+ util.ErrMsg('Argument required for ":LspServer trace"')
+ endif
+ else
+ util.ErrMsg($'LspServer - Unsupported argument "{args}"')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim
new file mode 100644
index 0000000..bfbdd77
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim
@@ -0,0 +1,1982 @@
+vim9script
+
+# LSP server functions
+#
+# The functions to send request messages to the language server are in this
+# file.
+#
+# Refer to https://microsoft.github.io/language-server-protocol/specification
+# for the Language Server Protocol (LSP) specificaiton.
+
+import './options.vim' as opt
+import './handlers.vim'
+import './util.vim'
+import './capabilities.vim'
+import './offset.vim'
+import './diag.vim'
+import './selection.vim'
+import './symbol.vim'
+import './textedit.vim'
+import './completion.vim'
+import './hover.vim'
+import './signature.vim'
+import './codeaction.vim'
+import './codelens.vim'
+import './callhierarchy.vim' as callhier
+import './typehierarchy.vim' as typehier
+import './inlayhints.vim'
+import './semantichighlight.vim'
+
+# LSP server standard output handler
+def Output_cb(lspserver: dict<any>, chan: channel, msg: any): void
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {msg->string()}')
+ endif
+ lspserver.data = msg
+ lspserver.processMessages()
+enddef
+
+# LSP server error output handler
+def Error_cb(lspserver: dict<any>, chan: channel, emsg: string): void
+ lspserver.errorLog(emsg)
+enddef
+
+# LSP server exit callback
+def Exit_cb(lspserver: dict<any>, job: job, status: number): void
+ util.WarnMsg($'{strftime("%m/%d/%y %T")}: LSP server ({lspserver.name}) exited with status {status}')
+ lspserver.running = false
+ lspserver.ready = false
+ lspserver.requests = {}
+enddef
+
+# Start a LSP server
+#
+def StartServer(lspserver: dict<any>, bnr: number): number
+ if lspserver.running
+ util.WarnMsg($'LSP server "{lspserver.name}" is already running')
+ return 0
+ endif
+
+ var cmd = [lspserver.path]
+ cmd->extend(lspserver.args)
+
+ var opts = {in_mode: 'lsp',
+ out_mode: 'lsp',
+ err_mode: 'raw',
+ noblock: 1,
+ out_cb: function(Output_cb, [lspserver]),
+ err_cb: function(Error_cb, [lspserver]),
+ exit_cb: function(Exit_cb, [lspserver])}
+
+ lspserver.data = ''
+ lspserver.caps = {}
+ lspserver.nextID = 1
+ lspserver.requests = {}
+ lspserver.omniCompletePending = false
+ lspserver.completionLazyDoc = false
+ lspserver.completionTriggerChars = []
+ lspserver.signaturePopup = -1
+
+ var job = cmd->job_start(opts)
+ if job->job_status() == 'fail'
+ util.ErrMsg($'Failed to start LSP server {lspserver.path}')
+ return 1
+ endif
+
+ # wait a little for the LSP server to start
+ sleep 10m
+
+ lspserver.job = job
+ lspserver.running = true
+
+ lspserver.initServer(bnr)
+
+ return 0
+enddef
+
+# process the "initialize" method reply from the LSP server
+# Result: InitializeResult
+def ServerInitReply(lspserver: dict<any>, initResult: dict<any>): void
+ if initResult->empty()
+ return
+ endif
+
+ var caps: dict<any> = initResult.capabilities
+ lspserver.caps = caps
+
+ for [key, val] in initResult->items()
+ if key == 'capabilities'
+ continue
+ endif
+
+ lspserver.caps[$'~additionalInitResult_{key}'] = val
+ endfor
+
+ capabilities.ProcessServerCaps(lspserver, caps)
+
+ if caps->has_key('completionProvider')
+ lspserver.completionTriggerChars =
+ caps.completionProvider->get('triggerCharacters', [])
+ lspserver.completionLazyDoc =
+ caps.completionProvider->get('resolveProvider', false)
+ endif
+
+ # send a "initialized" notification to server
+ lspserver.sendInitializedNotif()
+ # send any workspace configuration (optional)
+ if !lspserver.workspaceConfig->empty()
+ lspserver.sendWorkspaceConfig()
+ endif
+ lspserver.ready = true
+ if exists($'#User#LspServerReady{lspserver.name}')
+ exe $'doautocmd <nomodeline> User LspServerReady{lspserver.name}'
+ endif
+ # Used internally, and shouldn't be used by users
+ if exists($'#User#LspServerReady_{lspserver.id}')
+ exe $'doautocmd <nomodeline> User LspServerReady_{lspserver.id}'
+ endif
+
+ # set the server debug trace level
+ if lspserver.traceLevel != 'off'
+ lspserver.setTrace(lspserver.traceLevel)
+ endif
+
+ # if the outline window is opened, then request the symbols for the current
+ # buffer
+ if bufwinid('LSP-Outline') != -1
+ lspserver.getDocSymbols(@%, true)
+ endif
+
+ # Update the inlay hints (if enabled)
+ if opt.lspOptions.showInlayHints && (lspserver.isInlayHintProvider
+ || lspserver.isClangdInlayHintsProvider)
+ inlayhints.LspInlayHintsUpdateNow(bufnr())
+ endif
+enddef
+
+# Request: "initialize"
+# Param: InitializeParams
+def InitServer(lspserver: dict<any>, bnr: number)
+ # interface 'InitializeParams'
+ var initparams: dict<any> = {}
+ initparams.processId = getpid()
+ initparams.clientInfo = {
+ name: 'Vim',
+ version: v:versionlong->string(),
+ }
+
+ # Compute the rootpath (based on the directory of the buffer)
+ var rootPath = ''
+ var rootSearchFiles = lspserver.rootSearchFiles
+ var bufDir = bnr->bufname()->fnamemodify(':p:h')
+ if !rootSearchFiles->empty()
+ rootPath = util.FindNearestRootDir(bufDir, rootSearchFiles)
+ endif
+ if rootPath->empty()
+ var cwd = getcwd()
+
+ # bufDir is within cwd
+ var bufDirPrefix = bufDir[0 : cwd->strcharlen() - 1]
+ if &fileignorecase
+ ? bufDirPrefix ==? cwd
+ : bufDirPrefix == cwd
+ rootPath = cwd
+ else
+ rootPath = bufDir
+ endif
+ endif
+
+ lspserver.workspaceFolders = [rootPath]
+
+ var rootUri = util.LspFileToUri(rootPath)
+ initparams.rootPath = rootPath
+ initparams.rootUri = rootUri
+ initparams.workspaceFolders = [{
+ name: rootPath->fnamemodify(':t'),
+ uri: rootUri
+ }]
+
+ initparams.trace = 'off'
+ initparams.capabilities = capabilities.GetClientCaps()
+ if !lspserver.initializationOptions->empty()
+ initparams.initializationOptions = lspserver.initializationOptions
+ else
+ initparams.initializationOptions = {}
+ endif
+
+ lspserver.rpcInitializeRequest = initparams
+
+ lspserver.rpc_a('initialize', initparams, ServerInitReply)
+enddef
+
+# Send a "initialized" notification to the language server
+def SendInitializedNotif(lspserver: dict<any>)
+ # Notification: 'initialized'
+ # Params: InitializedParams
+ lspserver.sendNotification('initialized')
+enddef
+
+# Request: shutdown
+# Param: void
+def ShutdownServer(lspserver: dict<any>): void
+ lspserver.rpc('shutdown', {})
+enddef
+
+# Send a 'exit' notification to the language server
+def ExitServer(lspserver: dict<any>): void
+ # Notification: 'exit'
+ # Params: void
+ lspserver.sendNotification('exit')
+enddef
+
+# Stop a LSP server
+def StopServer(lspserver: dict<any>): number
+ if !lspserver.running
+ util.WarnMsg($'LSP server {lspserver.name} is not running')
+ return 0
+ endif
+
+ # Send the shutdown request to the server
+ lspserver.shutdownServer()
+
+ # Notify the server to exit
+ lspserver.exitServer()
+
+ # Wait for the server to process the exit notification and exit for a
+ # maximum of 2 seconds.
+ var maxCount: number = 1000
+ while lspserver.job->job_status() == 'run' && maxCount > 0
+ sleep 2m
+ maxCount -= 1
+ endwhile
+
+ if lspserver.job->job_status() == 'run'
+ lspserver.job->job_stop()
+ endif
+ lspserver.running = false
+ lspserver.ready = false
+ lspserver.requests = {}
+ return 0
+enddef
+
+# Set the language server trace level using the '$/setTrace' notification.
+# Supported values for "traceVal" are "off", "messages" and "verbose".
+def SetTrace(lspserver: dict<any>, traceVal: string)
+ # Notification: '$/setTrace'
+ # Params: SetTraceParams
+ var params = {value: traceVal}
+ lspserver.sendNotification('$/setTrace', params)
+enddef
+
+# Log a debug message to the LSP server debug file
+def TraceLog(lspserver: dict<any>, msg: string)
+ if lspserver.debug
+ util.TraceLog(lspserver.logfile, false, msg)
+ endif
+enddef
+
+# Log an error message to the LSP server error file
+def ErrorLog(lspserver: dict<any>, errmsg: string)
+ if lspserver.debug
+ util.TraceLog(lspserver.errfile, true, errmsg)
+ endif
+enddef
+
+# Return the next id for a LSP server request message
+def NextReqID(lspserver: dict<any>): number
+ var id = lspserver.nextID
+ lspserver.nextID = id + 1
+ return id
+enddef
+
+# create a LSP server request message
+def CreateRequest(lspserver: dict<any>, method: string): dict<any>
+ var req = {
+ jsonrpc: '2.0',
+ id: lspserver.nextReqID(),
+ method: method,
+ params: {}
+ }
+
+ # Save the request, so that the corresponding response can be processed
+ lspserver.requests->extend({[req.id->string()]: req})
+
+ return req
+enddef
+
+# create a LSP server response message
+def CreateResponse(lspserver: dict<any>, req_id: number): dict<any>
+ var resp = {
+ jsonrpc: '2.0',
+ id: req_id
+ }
+ return resp
+enddef
+
+# create a LSP server notification message
+def CreateNotification(lspserver: dict<any>, notif: string): dict<any>
+ var req = {
+ jsonrpc: '2.0',
+ method: notif,
+ params: {}
+ }
+
+ return req
+enddef
+
+# send a response message to the server
+def SendResponse(lspserver: dict<any>, request: dict<any>, result: any, error: dict<any>)
+ if (request.id->type() == v:t_string
+ && (request.id->trim() =~ '[^[:digit:]]\+'
+ || request.id->trim()->empty()))
+ || (request.id->type() != v:t_string && request.id->type() != v:t_number)
+ util.ErrMsg('request.id of response to LSP server is not a correct number')
+ return
+ endif
+ var resp: dict<any> = lspserver.createResponse(
+ request.id->type() == v:t_string ? request.id->str2nr() : request.id)
+ if error->empty()
+ resp->extend({result: result})
+ else
+ resp->extend({error: error})
+ endif
+ lspserver.sendMessage(resp)
+enddef
+
+# Send a request message to LSP server
+def SendMessage(lspserver: dict<any>, content: dict<any>): void
+ var job = lspserver.job
+ if job->job_status() != 'run'
+ # LSP server has exited
+ return
+ endif
+ job->ch_sendexpr(content)
+ if content->has_key('id')
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {content->string()}')
+ endif
+ endif
+enddef
+
+# Send a notification message to the language server
+def SendNotification(lspserver: dict<any>, method: string, params: any = {})
+ var notif: dict<any> = CreateNotification(lspserver, method)
+ notif.params->extend(params)
+ lspserver.sendMessage(notif)
+enddef
+
+# Translate an LSP error code into a readable string
+def LspGetErrorMessage(errcode: number): string
+ var errmap = {
+ -32001: 'UnknownErrorCode',
+ -32002: 'ServerNotInitialized',
+ -32600: 'InvalidRequest',
+ -32601: 'MethodNotFound',
+ -32602: 'InvalidParams',
+ -32603: 'InternalError',
+ -32700: 'ParseError',
+ -32800: 'RequestCancelled',
+ -32801: 'ContentModified',
+ -32802: 'ServerCancelled',
+ -32803: 'RequestFailed'
+ }
+
+ return errmap->get(errcode, errcode->string())
+enddef
+
+# Process a LSP server response error and display an error message.
+def ProcessLspServerError(method: string, responseError: dict<any>)
+ # request failed
+ var emsg: string = responseError.message
+ emsg ..= $', error = {LspGetErrorMessage(responseError.code)}'
+ if responseError->has_key('data')
+ emsg ..= $', data = {responseError.data->string()}'
+ endif
+ util.ErrMsg($'request {method} failed ({emsg})')
+enddef
+
+# Send a sync RPC request message to the LSP server and return the received
+# reply. In case of an error, an empty Dict is returned.
+def Rpc(lspserver: dict<any>, method: string, params: any, handleError: bool = true): dict<any>
+ var req = {
+ method: method,
+ params: params
+ }
+
+ var job = lspserver.job
+ if job->job_status() != 'run'
+ # LSP server has exited
+ return {}
+ endif
+
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
+ endif
+
+ # Do the synchronous RPC call
+ var reply = job->ch_evalexpr(req)
+
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
+ endif
+
+ if reply->has_key('result')
+ # successful reply
+ return reply
+ endif
+
+ if reply->has_key('error') && handleError
+ # request failed
+ ProcessLspServerError(method, reply.error)
+ endif
+
+ return {}
+enddef
+
+# LSP server asynchronous RPC callback
+def AsyncRpcCb(lspserver: dict<any>, method: string, RpcCb: func, chan: channel, reply: dict<any>)
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
+ endif
+
+ if reply->empty()
+ return
+ endif
+
+ if reply->has_key('error')
+ # request failed
+ ProcessLspServerError(method, reply.error)
+ return
+ endif
+
+ if !reply->has_key('result')
+ util.ErrMsg($'request {method} failed (no result)')
+ return
+ endif
+
+ RpcCb(lspserver, reply.result)
+enddef
+
+# Send a async RPC request message to the LSP server with a callback function.
+# Returns the LSP message id. This id can be used to cancel the RPC request
+# (if needed). Returns -1 on error.
+def AsyncRpc(lspserver: dict<any>, method: string, params: any, Cbfunc: func): number
+ var req = {
+ method: method,
+ params: params
+ }
+
+ var job = lspserver.job
+ if job->job_status() != 'run'
+ # LSP server has exited
+ return -1
+ endif
+
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
+ endif
+
+ # Do the asynchronous RPC call
+ var Fn = function('AsyncRpcCb', [lspserver, method, Cbfunc])
+
+ var reply: dict<any>
+ if get(g:, 'LSPTest')
+ # When running LSP tests, make this a synchronous RPC call
+ reply = Rpc(lspserver, method, params)
+ Fn(test_null_channel(), reply)
+ else
+ # Otherwise, make an asynchronous RPC call
+ reply = job->ch_sendexpr(req, {callback: Fn})
+ endif
+ if reply->empty()
+ return -1
+ endif
+
+ return reply.id
+enddef
+
+# Wait for a response message from the LSP server for the request "req"
+# Waits for a maximum of 5 seconds
+def WaitForResponse(lspserver: dict<any>, req: dict<any>)
+ var maxCount: number = 2500
+ var key: string = req.id->string()
+
+ while lspserver.requests->has_key(key) && maxCount > 0
+ sleep 2m
+ maxCount -= 1
+ endwhile
+enddef
+
+# Returns true when the "lspserver" has "feature" enabled.
+# By default, all the features of a lsp server are enabled.
+def FeatureEnabled(lspserver: dict<any>, feature: string): bool
+ return lspserver.features->get(feature, true)
+enddef
+
+# Retrieve the Workspace configuration asked by the server.
+# Request: workspace/configuration
+def WorkspaceConfigGet(lspserver: dict<any>, configItem: dict<any>): dict<any>
+ if lspserver.workspaceConfig->empty()
+ return {}
+ endif
+ if !configItem->has_key('section') || configItem.section->empty()
+ return lspserver.workspaceConfig
+ endif
+ var config: dict<any> = lspserver.workspaceConfig
+ for part in configItem.section->split('\.')
+ if !config->has_key(part)
+ return {}
+ endif
+ config = config[part]
+ endfor
+ return config
+enddef
+
+# Update semantic highlighting for buffer "bnr"
+# Request: textDocument/semanticTokens/full or
+# textDocument/semanticTokens/full/delta
+def SemanticHighlightUpdate(lspserver: dict<any>, bnr: number)
+ if !lspserver.isSemanticTokensProvider
+ return
+ endif
+
+ # Send the pending buffer changes to the language server
+ bnr->listener_flush()
+
+ var method = 'textDocument/semanticTokens/full'
+ var params: dict<any> = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr)
+ }
+ }
+
+ # Should we send a semantic tokens delta request instead of a full request?
+ if lspserver.semanticTokensDelta
+ var prevResultId: string = ''
+ prevResultId = bnr->getbufvar('LspSemanticResultId', '')
+ if prevResultId != ''
+ # semantic tokens delta request
+ params.previousResultId = prevResultId
+ method ..= '/delta'
+ endif
+ endif
+
+ var reply = lspserver.rpc(method, params)
+
+ if reply->empty() || reply.result->empty()
+ return
+ endif
+
+ semantichighlight.UpdateTokens(lspserver, bnr, reply.result)
+enddef
+
+# Send a "workspace/didChangeConfiguration" notification to the language
+# server.
+def SendWorkspaceConfig(lspserver: dict<any>)
+ # Params: DidChangeConfigurationParams
+ var params = {settings: lspserver.workspaceConfig}
+ lspserver.sendNotification('workspace/didChangeConfiguration', params)
+enddef
+
+# Send a file/document opened notification to the language server.
+def TextdocDidOpen(lspserver: dict<any>, bnr: number, ftype: string): void
+ # Notification: 'textDocument/didOpen'
+ # Params: DidOpenTextDocumentParams
+ var params = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr),
+ languageId: ftype,
+ # Use Vim 'changedtick' as the LSP document version number
+ version: bnr->getbufvar('changedtick'),
+ text: bnr->getbufline(1, '$')->join("\n") .. "\n"
+ }
+ }
+ lspserver.sendNotification('textDocument/didOpen', params)
+enddef
+
+# Send a file/document closed notification to the language server.
+def TextdocDidClose(lspserver: dict<any>, bnr: number): void
+ # Notification: 'textDocument/didClose'
+ # Params: DidCloseTextDocumentParams
+ var params = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr)
+ }
+ }
+ lspserver.sendNotification('textDocument/didClose', params)
+enddef
+
+# Send a file/document change notification to the language server.
+# Params: DidChangeTextDocumentParams
+def TextdocDidChange(lspserver: dict<any>, bnr: number, start: number,
+ end: number, added: number,
+ changes: list<dict<number>>): void
+ # Notification: 'textDocument/didChange'
+ # Params: DidChangeTextDocumentParams
+
+ # var changeset: list<dict<any>>
+
+ ##### FIXME: Sending specific buffer changes to the LSP server doesn't
+ ##### work properly as the computed line range numbers is not correct.
+ ##### For now, send the entire buffer content to LSP server.
+ # # Range
+ # for change in changes
+ # var lines: string
+ # var start_lnum: number
+ # var end_lnum: number
+ # var start_col: number
+ # var end_col: number
+ # if change.added == 0
+ # # lines changed
+ # start_lnum = change.lnum - 1
+ # end_lnum = change.end - 1
+ # lines = getbufline(bnr, change.lnum, change.end - 1)->join("\n") .. "\n"
+ # start_col = 0
+ # end_col = 0
+ # elseif change.added > 0
+ # # lines added
+ # start_lnum = change.lnum - 1
+ # end_lnum = change.lnum - 1
+ # start_col = 0
+ # end_col = 0
+ # lines = getbufline(bnr, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n"
+ # else
+ # # lines removed
+ # start_lnum = change.lnum - 1
+ # end_lnum = change.lnum + (-change.added) - 1
+ # start_col = 0
+ # end_col = 0
+ # lines = ''
+ # endif
+ # var range: dict<dict<number>> = {'start': {'line': start_lnum, 'character': start_col}, 'end': {'line': end_lnum, 'character': end_col}}
+ # changeset->add({'range': range, 'text': lines})
+ # endfor
+
+ var params = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr),
+ # Use Vim 'changedtick' as the LSP document version number
+ version: bnr->getbufvar('changedtick')
+ },
+ contentChanges: [
+ {text: bnr->getbufline(1, '$')->join("\n") .. "\n"}
+ ]
+ }
+ lspserver.sendNotification('textDocument/didChange', params)
+enddef
+
+# Return the current cursor position as a LSP position.
+# find_ident will search for a identifier in front of the cursor, just like
+# CTRL-] and c_CTRL-R_CTRL-W does.
+#
+# LSP line and column numbers start from zero, whereas Vim line and column
+# numbers start from one. The LSP column number is the character index in the
+# line and not the byte index in the line.
+def GetPosition(lspserver: dict<any>, find_ident: bool): dict<number>
+ var lnum: number = line('.') - 1
+ var col: number = charcol('.') - 1
+ var line = getline('.')
+
+ if find_ident
+ # 1. skip to start of identifier
+ while line[col] != '' && line[col] !~ '\k'
+ col = col + 1
+ endwhile
+
+ # 2. back up to start of identifier
+ while col > 0 && line[col - 1] =~ '\k'
+ col = col - 1
+ endwhile
+ endif
+
+ # Compute character index counting composing characters as separate
+ # characters
+ var pos = {line: lnum, character: util.GetCharIdxWithCompChar(line, col)}
+ lspserver.encodePosition(bufnr(), pos)
+
+ return pos
+enddef
+
+# Return the current file name and current cursor position as a LSP
+# TextDocumentPositionParams structure
+def GetTextDocPosition(lspserver: dict<any>, find_ident: bool): dict<dict<any>>
+ # interface TextDocumentIdentifier
+ # interface Position
+ return {textDocument: {uri: util.LspFileToUri(@%)},
+ position: lspserver.getPosition(find_ident)}
+enddef
+
+# Get a list of completion items.
+# Request: "textDocument/completion"
+# Param: CompletionParams
+def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: string): void
+ # Check whether LSP server supports completion
+ if !lspserver.isCompletionProvider
+ util.ErrMsg('LSP server does not support completion')
+ return
+ endif
+
+ var fname = @%
+ if fname->empty()
+ return
+ endif
+
+ # interface CompletionParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ # interface CompletionContext
+ params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
+
+ lspserver.rpc_a('textDocument/completion', params,
+ completion.CompletionReply)
+enddef
+
+# Get lazy properties for a completion item.
+# Request: "completionItem/resolve"
+# Param: CompletionItem
+def ResolveCompletion(lspserver: dict<any>, item: dict<any>, sync: bool = false): dict<any>
+ # Check whether LSP server supports completion item resolve
+ if !lspserver.isCompletionResolveProvider
+ return {}
+ endif
+
+ # interface CompletionItem
+ if sync
+ var reply = lspserver.rpc('completionItem/resolve', item)
+ if !reply->empty() && !reply.result->empty()
+ return reply.result
+ endif
+ else
+ lspserver.rpc_a('completionItem/resolve', item,
+ completion.CompletionResolveReply)
+ endif
+ return {}
+enddef
+
+# Jump to or peek a symbol location.
+#
+# Send 'msg' to a LSP server and process the reply. 'msg' is one of the
+# following:
+# textDocument/definition
+# textDocument/declaration
+# textDocument/typeDefinition
+# textDocument/implementation
+#
+# Process the LSP server reply and jump to the symbol location. Before
+# jumping to the symbol location, save the current cursor position in the tag
+# stack.
+#
+# If 'peekSymbol' is true, then display the symbol location in the preview
+# window but don't jump to the symbol location.
+#
+# Result: Location | Location[] | LocationLink[] | null
+def GotoSymbolLoc(lspserver: dict<any>, msg: string, peekSymbol: bool,
+ cmdmods: string, count: number)
+ var reply = lspserver.rpc(msg, lspserver.getTextDocPosition(true), false)
+ if reply->empty() || reply.result->empty()
+ var emsg: string
+ if msg == 'textDocument/declaration'
+ emsg = 'symbol declaration is not found'
+ elseif msg == 'textDocument/typeDefinition'
+ emsg = 'symbol type definition is not found'
+ elseif msg == 'textDocument/implementation'
+ emsg = 'symbol implementation is not found'
+ else
+ emsg = 'symbol definition is not found'
+ endif
+
+ util.WarnMsg(emsg)
+ return
+ endif
+
+ var result = reply.result
+ var location: dict<any>
+ if result->type() == v:t_list
+ if count == 0
+ # When there are multiple symbol locations, and a specific one isn't
+ # requested with 'count', display the locations in a location list.
+ if result->len() > 1
+ var title: string = ''
+ if msg == 'textDocument/declaration'
+ title = 'Declarations'
+ elseif msg == 'textDocument/typeDefinition'
+ title = 'Type Definitions'
+ elseif msg == 'textDocument/implementation'
+ title = 'Implementations'
+ else
+ title = 'Definitions'
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the symbol locations
+ result->map((_, loc) => {
+ lspserver.decodeLocation(loc)
+ return loc
+ })
+ endif
+
+ symbol.ShowLocations(lspserver, result, peekSymbol, title)
+ return
+ endif
+ endif
+
+ # Select the location requested in 'count'
+ var idx = count - 1
+ if idx >= result->len()
+ idx = result->len() - 1
+ endif
+ location = result[idx]
+ else
+ location = result
+ endif
+ lspserver.decodeLocation(location)
+
+ symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods)
+enddef
+
+# Request: "textDocument/definition"
+# Param: DefinitionParams
+def GotoDefinition(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a definition
+ if !lspserver.isDefinitionProvider
+ util.ErrMsg('Jumping to a symbol definition is not supported')
+ return
+ endif
+
+ # interface DefinitionParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/definition', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/declaration"
+# Param: DeclarationParams
+def GotoDeclaration(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a declaration
+ if !lspserver.isDeclarationProvider
+ util.ErrMsg('Jumping to a symbol declaration is not supported')
+ return
+ endif
+
+ # interface DeclarationParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/declaration', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/typeDefinition"
+# Param: TypeDefinitionParams
+def GotoTypeDef(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a type definition
+ if !lspserver.isTypeDefinitionProvider
+ util.ErrMsg('Jumping to a symbol type definition is not supported')
+ return
+ endif
+
+ # interface TypeDefinitionParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/implementation"
+# Param: ImplementationParams
+def GotoImplementation(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a implementation
+ if !lspserver.isImplementationProvider
+ util.ErrMsg('Jumping to a symbol implementation is not supported')
+ return
+ endif
+
+ # interface ImplementationParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/implementation', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/switchSourceHeader"
+# Param: TextDocumentIdentifier
+# Clangd specific extension
+def SwitchSourceHeader(lspserver: dict<any>)
+ var param = {
+ uri: util.LspFileToUri(@%)
+ }
+ var reply = lspserver.rpc('textDocument/switchSourceHeader', param)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg('Source/Header file is not found')
+ return
+ endif
+
+ # process the 'textDocument/switchSourceHeader' reply from the LSP server
+ # Result: URI | null
+ var fname = util.LspUriToFile(reply.result)
+ # TODO: Add support for cmd modifiers
+ if (&modified && !&hidden) || &buftype != ''
+ # if the current buffer has unsaved changes and 'hidden' is not set,
+ # or if the current buffer is a special buffer, then ask to save changes
+ exe $'confirm edit {fname}'
+ else
+ exe $'edit {fname}'
+ endif
+enddef
+
+# get symbol signature help.
+# Request: "textDocument/signatureHelp"
+# Param: SignatureHelpParams
+def ShowSignature(lspserver: dict<any>): void
+ # Check whether LSP server supports signature help
+ if !lspserver.isSignatureHelpProvider
+ util.ErrMsg('LSP server does not support signature help')
+ return
+ endif
+
+ # interface SignatureHelpParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ lspserver.rpc_a('textDocument/signatureHelp', params,
+ signature.SignatureHelp)
+enddef
+
+# Send a file/document saved notification to the language server
+def DidSaveFile(lspserver: dict<any>, bnr: number): void
+ # Check whether the LSP server supports the didSave notification
+ if !lspserver.supportsDidSave
+ # LSP server doesn't support text document synchronization
+ return
+ endif
+
+ # Notification: 'textDocument/didSave'
+ # Params: DidSaveTextDocumentParams
+ var params: dict<any> = {textDocument: {uri: util.LspBufnrToUri(bnr)}}
+
+ if lspserver.caps.textDocumentSync->type() == v:t_dict
+ && lspserver.caps.textDocumentSync->has_key('save')
+ if lspserver.caps.textDocumentSync.save->type() == v:t_dict
+ && lspserver.caps.textDocumentSync.save->has_key('includeText')
+ && lspserver.caps.textDocumentSync.save.includeText
+ params.text = bnr->getbufline(1, '$')->join("\n") .. "\n"
+ endif
+ endif
+
+ lspserver.sendNotification('textDocument/didSave', params)
+enddef
+
+# get the hover information
+# Request: "textDocument/hover"
+# Param: HoverParams
+def ShowHoverInfo(lspserver: dict<any>, cmdmods: string): void
+ # Check whether LSP server supports getting hover information.
+ # caps->hoverProvider can be a "boolean" or "HoverOptions"
+ if !lspserver.isHoverProvider
+ return
+ endif
+
+ # interface HoverParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
+ hover.HoverReply(lspserver, reply, cmdmods)
+ })
+enddef
+
+# Request: "textDocument/references"
+# Param: ReferenceParams
+def ShowReferences(lspserver: dict<any>, peek: bool): void
+ # Check whether LSP server supports getting reference information
+ if !lspserver.isReferencesProvider
+ util.ErrMsg('LSP server does not support showing references')
+ return
+ endif
+
+ # interface ReferenceParams
+ # interface TextDocumentPositionParams
+ var param: dict<any>
+ param = lspserver.getTextDocPosition(true)
+ param.context = {includeDeclaration: true}
+ var reply = lspserver.rpc('textDocument/references', param)
+
+ # Result: Location[] | null
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg('No references found')
+ return
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the reference locations
+ reply.result->map((_, loc) => {
+ lspserver.decodeLocation(loc)
+ return loc
+ })
+ endif
+
+ symbol.ShowLocations(lspserver, reply.result, peek, 'Symbol References')
+enddef
+
+# process the 'textDocument/documentHighlight' reply from the LSP server
+# Result: DocumentHighlight[] | null
+def DocHighlightReply(lspserver: dict<any>, docHighlightReply: any,
+ bnr: number, cmdmods: string): void
+ if docHighlightReply->empty()
+ if cmdmods !~ 'silent'
+ util.WarnMsg($'No highlight for the current position')
+ endif
+ return
+ endif
+
+ for docHL in docHighlightReply
+ lspserver.decodeRange(bnr, docHL.range)
+ var kind: number = docHL->get('kind', 1)
+ var propName: string
+ if kind == 2
+ # Read-access
+ propName = 'LspReadRef'
+ elseif kind == 3
+ # Write-access
+ propName = 'LspWriteRef'
+ else
+ # textual reference
+ propName = 'LspTextRef'
+ endif
+ try
+ var docHL_range = docHL.range
+ var docHL_start = docHL_range.start
+ var docHL_end = docHL_range.end
+ prop_add(docHL_start.line + 1,
+ util.GetLineByteFromPos(bnr, docHL_start) + 1,
+ {end_lnum: docHL_end.line + 1,
+ end_col: util.GetLineByteFromPos(bnr, docHL_end) + 1,
+ bufnr: bnr,
+ type: propName})
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ # Highlight replies arrive asynchronously and the document might have
+ # been modified in the mean time. As the reply is stale, ignore invalid
+ # line number and column number errors.
+ endtry
+ endfor
+enddef
+
+# Request: "textDocument/documentHighlight"
+# Param: DocumentHighlightParams
+def DocHighlight(lspserver: dict<any>, bnr: number, cmdmods: string): void
+ # Check whether LSP server supports getting highlight information
+ if !lspserver.isDocumentHighlightProvider
+ util.ErrMsg('LSP server does not support document highlight')
+ return
+ endif
+
+ # Send the pending buffer changes to the language server
+ bnr->listener_flush()
+
+ # interface DocumentHighlightParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
+ DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
+ })
+enddef
+
+# Request: "textDocument/documentSymbol"
+# Param: DocumentSymbolParams
+def GetDocSymbols(lspserver: dict<any>, fname: string, showOutline: bool): void
+ # Check whether LSP server supports getting document symbol information
+ if !lspserver.isDocumentSymbolProvider
+ util.ErrMsg('LSP server does not support getting list of symbols')
+ return
+ endif
+
+ # interface DocumentSymbolParams
+ # interface TextDocumentIdentifier
+ var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+ lspserver.rpc_a('textDocument/documentSymbol', params, (_, reply) => {
+ if showOutline
+ symbol.DocSymbolOutline(lspserver, reply, fname)
+ else
+ symbol.DocSymbolPopup(lspserver, reply, fname)
+ endif
+ })
+enddef
+
+# Request: "textDocument/formatting"
+# Param: DocumentFormattingParams
+# or
+# Request: "textDocument/rangeFormatting"
+# Param: DocumentRangeFormattingParams
+def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
+ start_lnum: number, end_lnum: number)
+ # Check whether LSP server supports formatting documents
+ if !lspserver.isDocumentFormattingProvider
+ util.ErrMsg('LSP server does not support formatting documents')
+ return
+ endif
+
+ var cmd: string
+ if rangeFormat
+ cmd = 'textDocument/rangeFormatting'
+ else
+ cmd = 'textDocument/formatting'
+ endif
+
+ # interface DocumentFormattingParams
+ # interface TextDocumentIdentifier
+ # interface FormattingOptions
+ var fmtopts: dict<any> = {
+ tabSize: shiftwidth(),
+ insertSpaces: &expandtab ? true : false,
+ }
+ var param = {
+ textDocument: {
+ uri: util.LspFileToUri(fname)
+ },
+ options: fmtopts
+ }
+
+ if rangeFormat
+ var r: dict<dict<number>> = {
+ start: {line: start_lnum - 1, character: 0},
+ end: {line: end_lnum - 1, character: charcol([end_lnum, '$']) - 1}}
+ param.range = r
+ endif
+
+ var reply = lspserver.rpc(cmd, param)
+
+ # result: TextEdit[] | null
+
+ if reply->empty() || reply.result->empty()
+ # nothing to format
+ return
+ endif
+
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ # file is already removed
+ return
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the reference locations
+ reply.result->map((_, textEdit) => {
+ lspserver.decodeRange(bnr, textEdit.range)
+ return textEdit
+ })
+ endif
+
+ # interface TextEdit
+ # Apply each of the text edit operations
+ var save_cursor: list<number> = getcurpos()
+ textedit.ApplyTextEdits(bnr, reply.result)
+ save_cursor->setpos('.')
+enddef
+
+# Request: "textDocument/prepareCallHierarchy"
+def PrepareCallHierarchy(lspserver: dict<any>): dict<any>
+ # interface CallHierarchyPrepareParams
+ # interface TextDocumentPositionParams
+ var param: dict<any>
+ param = lspserver.getTextDocPosition(false)
+ var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
+ if reply->empty() || reply.result->empty()
+ return {}
+ endif
+
+ # Result: CallHierarchyItem[] | null
+ var choice: number = 1
+ if reply.result->len() > 1
+ var items: list<string> = ['Select a Call Hierarchy Item:']
+ for i in reply.result->len()->range()
+ items->add(printf("%d. %s", i + 1, reply.result[i].name))
+ endfor
+ choice = items->inputlist()
+ if choice < 1 || choice > items->len()
+ return {}
+ endif
+ endif
+
+ return reply.result[choice - 1]
+enddef
+
+# Request: "callHierarchy/incomingCalls"
+def IncomingCalls(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports call hierarchy
+ if !lspserver.isCallHierarchyProvider
+ util.ErrMsg('LSP server does not support call hierarchy')
+ return
+ endif
+
+ callhier.IncomingCalls(lspserver)
+enddef
+
+def GetIncomingCalls(lspserver: dict<any>, item_arg: dict<any>): any
+ # Request: "callHierarchy/incomingCalls"
+ # Param: CallHierarchyIncomingCallsParams
+ var param = {
+ item: item_arg
+ }
+ var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
+ if reply->empty()
+ return null
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the incoming call locations
+ var bnr = util.LspUriToBufnr(item_arg.uri)
+ reply.result->map((_, hierItem) => {
+ lspserver.decodeRange(bnr, hierItem.from.range)
+ return hierItem
+ })
+ endif
+
+ return reply.result
+enddef
+
+# Request: "callHierarchy/outgoingCalls"
+def OutgoingCalls(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports call hierarchy
+ if !lspserver.isCallHierarchyProvider
+ util.ErrMsg('LSP server does not support call hierarchy')
+ return
+ endif
+
+ callhier.OutgoingCalls(lspserver)
+enddef
+
+def GetOutgoingCalls(lspserver: dict<any>, item_arg: dict<any>): any
+ # Request: "callHierarchy/outgoingCalls"
+ # Param: CallHierarchyOutgoingCallsParams
+ var param = {
+ item: item_arg
+ }
+ var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
+ if reply->empty()
+ return null
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the outgoing call locations
+ var bnr = util.LspUriToBufnr(item_arg.uri)
+ reply.result->map((_, hierItem) => {
+ lspserver.decodeRange(bnr, hierItem.to.range)
+ return hierItem
+ })
+ endif
+
+ return reply.result
+enddef
+
+# Request: "textDocument/inlayHint"
+# Inlay hints.
+def InlayHintsShow(lspserver: dict<any>, bnr: number)
+ # Check whether LSP server supports type hierarchy
+ if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
+ util.ErrMsg('LSP server does not support inlay hint')
+ return
+ endif
+
+ # Send the pending buffer changes to the language server
+ bnr->listener_flush()
+
+ var binfo = bnr->getbufinfo()
+ if binfo->empty()
+ return
+ endif
+ var lastlnum = binfo[0].linecount
+ var lastline = bnr->getbufline('$')
+ var lastcol = 1
+ if !lastline->empty() && !lastline[0]->empty()
+ lastcol = lastline[0]->strchars()
+ endif
+ var param = {
+ textDocument: {uri: util.LspBufnrToUri(bnr)},
+ range:
+ {
+ start: {line: 0, character: 0},
+ end: {line: lastlnum - 1, character: lastcol - 1}
+ }
+ }
+
+ lspserver.encodeRange(bnr, param.range)
+
+ var msg: string
+ if lspserver.isClangdInlayHintsProvider
+ # clangd-style inlay hints
+ msg = 'clangd/inlayHints'
+ else
+ msg = 'textDocument/inlayHint'
+ endif
+ var reply = lspserver.rpc_a(msg, param, (_, reply) => {
+ inlayhints.InlayHintsReply(lspserver, bnr, reply)
+ })
+enddef
+
+def DecodeTypeHierarchy(lspserver: dict<any>, isSuper: bool, typeHier: dict<any>)
+ if !lspserver.needOffsetEncoding
+ return
+ endif
+ var bnr = util.LspUriToBufnr(typeHier.uri)
+ lspserver.decodeRange(bnr, typeHier.range)
+ lspserver.decodeRange(bnr, typeHier.selectionRange)
+ var subType: list<dict<any>>
+ if isSuper
+ subType = typeHier->get('parents', [])
+ else
+ subType = typeHier->get('children', [])
+ endif
+ if !subType->empty()
+ # Decode the position encoding in all the type hierarchy items
+ subType->map((_, typeHierItem) => {
+ DecodeTypeHierarchy(lspserver, isSuper, typeHierItem)
+ return typeHierItem
+ })
+ endif
+enddef
+
+# Request: "textDocument/typehierarchy"
+# Support the clangd version of type hierarchy retrieval method.
+# The method described in the LSP 3.17.0 standard is not supported as clangd
+# doesn't support that method.
+def TypeHierarchy(lspserver: dict<any>, direction: number)
+ # Check whether LSP server supports type hierarchy
+ if !lspserver.isTypeHierarchyProvider
+ util.ErrMsg('LSP server does not support type hierarchy')
+ return
+ endif
+
+ # interface TypeHierarchy
+ # interface TextDocumentPositionParams
+ var param: dict<any>
+ param = lspserver.getTextDocPosition(false)
+ # 0: children, 1: parent, 2: both
+ param.direction = direction
+ param.resolve = 5
+ var reply = lspserver.rpc('textDocument/typeHierarchy', param)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg('No type hierarchy available')
+ return
+ endif
+
+ var isSuper = (direction == 1)
+
+ DecodeTypeHierarchy(lspserver, isSuper, reply.result)
+
+ typehier.ShowTypeHierarchy(lspserver, isSuper, reply.result)
+enddef
+
+# Decode the ranges in "WorkspaceEdit"
+def DecodeWorkspaceEdit(lspserver: dict<any>, workspaceEdit: dict<any>)
+ if !lspserver.needOffsetEncoding
+ return
+ endif
+ if workspaceEdit->has_key('changes')
+ for [uri, changes] in workspaceEdit.changes->items()
+ var bnr: number = util.LspUriToBufnr(uri)
+ if bnr <= 0
+ continue
+ endif
+ # Decode the position encoding in all the text edit locations
+ changes->map((_, textEdit) => {
+ lspserver.decodeRange(bnr, textEdit.range)
+ return textEdit
+ })
+ endfor
+ endif
+
+ if workspaceEdit->has_key('documentChanges')
+ for change in workspaceEdit.documentChanges
+ if !change->has_key('kind')
+ var bnr: number = util.LspUriToBufnr(change.textDocument.uri)
+ if bnr <= 0
+ continue
+ endif
+ # Decode the position encoding in all the text edit locations
+ change.edits->map((_, textEdit) => {
+ lspserver.decodeRange(bnr, textEdit.range)
+ return textEdit
+ })
+ endif
+ endfor
+ endif
+enddef
+
+# Request: "textDocument/rename"
+# Param: RenameParams
+def RenameSymbol(lspserver: dict<any>, newName: string)
+ # Check whether LSP server supports rename operation
+ if !lspserver.isRenameProvider
+ util.ErrMsg('LSP server does not support rename operation')
+ return
+ endif
+
+ # interface RenameParams
+ # interface TextDocumentPositionParams
+ var param: dict<any> = {}
+ param = lspserver.getTextDocPosition(true)
+ param.newName = newName
+
+ var reply = lspserver.rpc('textDocument/rename', param)
+
+ # Result: WorkspaceEdit | null
+ if reply->empty() || reply.result->empty()
+ # nothing to rename
+ return
+ endif
+
+ # result: WorkspaceEdit
+ DecodeWorkspaceEdit(lspserver, reply.result)
+ textedit.ApplyWorkspaceEdit(reply.result)
+enddef
+
+# Decode the range in "CodeAction"
+def DecodeCodeAction(lspserver: dict<any>, actionList: list<dict<any>>)
+ if !lspserver.needOffsetEncoding
+ return
+ endif
+ actionList->map((_, act) => {
+ if !act->has_key('disabled') && act->has_key('edit')
+ DecodeWorkspaceEdit(lspserver, act.edit)
+ endif
+ return act
+ })
+enddef
+
+# Request: "textDocument/codeAction"
+# Param: CodeActionParams
+def CodeAction(lspserver: dict<any>, fname_arg: string, line1: number,
+ line2: number, query: string)
+ # Check whether LSP server supports code action operation
+ if !lspserver.isCodeActionProvider
+ util.ErrMsg('LSP server does not support code action operation')
+ return
+ endif
+
+ # interface CodeActionParams
+ var params: dict<any> = {}
+ var fname: string = fname_arg->fnamemodify(':p')
+ var bnr: number = fname_arg->bufnr()
+ var r: dict<dict<number>> = {
+ start: {
+ line: line1 - 1,
+ character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
+ },
+ end: {
+ line: line2 - 1,
+ character: util.GetCharIdxWithCompChar(getline(line2), charcol([line2, '$']) - 1)
+ }
+ }
+ lspserver.encodeRange(bnr, r)
+ params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
+ var d: list<dict<any>> = []
+ for lnum in range(line1, line2)
+ var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(bnr, lnum, lspserver)->deepcopy()
+ if lspserver.needOffsetEncoding
+ diagsInfo->map((_, di) => {
+ lspserver.encodeRange(bnr, di.range)
+ return di
+ })
+ endif
+ d->extend(diagsInfo)
+ endfor
+ params->extend({context: {diagnostics: d, triggerKind: 1}})
+
+ var reply = lspserver.rpc('textDocument/codeAction', params)
+
+ # Result: (Command | CodeAction)[] | null
+ if reply->empty() || reply.result->empty()
+ # no action can be performed
+ util.WarnMsg('No code action is available')
+ return
+ endif
+
+ DecodeCodeAction(lspserver, reply.result)
+
+ codeaction.ApplyCodeAction(lspserver, reply.result, query)
+enddef
+
+# Request: "textDocument/codeLens"
+# Param: CodeLensParams
+def CodeLens(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports code lens operation
+ if !lspserver.isCodeLensProvider
+ util.ErrMsg('LSP server does not support code lens operation')
+ return
+ endif
+
+ var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+ var reply = lspserver.rpc('textDocument/codeLens', params)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg($'No code lens actions found for the current file')
+ return
+ endif
+
+ var bnr = fname->bufnr()
+
+ # Decode the position encoding in all the code lens items
+ if lspserver.needOffsetEncoding
+ reply.result->map((_, codeLensItem) => {
+ lspserver.decodeRange(bnr, codeLensItem.range)
+ return codeLensItem
+ })
+ endif
+
+ codelens.ProcessCodeLens(lspserver, bnr, reply.result)
+enddef
+
+# Request: "codeLens/resolve"
+# Param: CodeLens
+def ResolveCodeLens(lspserver: dict<any>, bnr: number,
+ codeLens: dict<any>): dict<any>
+ if !lspserver.isCodeLensResolveProvider
+ return {}
+ endif
+
+ if lspserver.needOffsetEncoding
+ lspserver.encodeRange(bnr, codeLens.range)
+ endif
+
+ var reply = lspserver.rpc('codeLens/resolve', codeLens)
+ if reply->empty()
+ return {}
+ endif
+
+ var codeLensItem: dict<any> = reply.result
+
+ # Decode the position encoding in the code lens item
+ if lspserver.needOffsetEncoding
+ lspserver.decodeRange(bnr, codeLensItem.range)
+ endif
+
+ return codeLensItem
+enddef
+
+# List project-wide symbols matching query string
+# Request: "workspace/symbol"
+# Param: WorkspaceSymbolParams
+def WorkspaceQuerySymbols(lspserver: dict<any>, query: string, firstCall: bool, cmdmods: string = '')
+ # Check whether the LSP server supports listing workspace symbols
+ if !lspserver.isWorkspaceSymbolProvider
+ util.ErrMsg('LSP server does not support listing workspace symbols')
+ return
+ endif
+
+ # Param: WorkspaceSymbolParams
+ var param = {
+ query: query
+ }
+ var reply = lspserver.rpc('workspace/symbol', param)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg($'Symbol "{query}" is not found')
+ return
+ endif
+
+ var symInfo: list<dict<any>> = reply.result
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the symbol locations
+ symInfo->map((_, sym) => {
+ if sym->has_key('location')
+ lspserver.decodeLocation(sym.location)
+ endif
+ return sym
+ })
+ endif
+
+ if firstCall && symInfo->len() == 1
+ # If there is only one symbol, then jump to the symbol location
+ var symLoc: dict<any> = symInfo[0]->get('location', {})
+ if !symLoc->empty()
+ symbol.GotoSymbol(lspserver, symLoc, false, cmdmods)
+ endif
+ else
+ symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods)
+ endif
+enddef
+
+# Add a workspace folder to the language server.
+def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
+ if !lspserver.caps->has_key('workspace')
+ || !lspserver.caps.workspace->has_key('workspaceFolders')
+ || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
+ || !lspserver.caps.workspace.workspaceFolders.supported
+ util.ErrMsg('LSP server does not support workspace folders')
+ return
+ endif
+
+ if lspserver.workspaceFolders->index(dirName) != -1
+ util.ErrMsg($'{dirName} is already part of this workspace')
+ return
+ endif
+
+ # Notification: 'workspace/didChangeWorkspaceFolders'
+ # Params: DidChangeWorkspaceFoldersParams
+ var params = {event: {added: [dirName], removed: []}}
+ lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
+
+ lspserver.workspaceFolders->add(dirName)
+enddef
+
+# Remove a workspace folder from the language server.
+def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
+ if !lspserver.caps->has_key('workspace')
+ || !lspserver.caps.workspace->has_key('workspaceFolders')
+ || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
+ || !lspserver.caps.workspace.workspaceFolders.supported
+ util.ErrMsg('LSP server does not support workspace folders')
+ return
+ endif
+
+ var idx: number = lspserver.workspaceFolders->index(dirName)
+ if idx == -1
+ util.ErrMsg($'{dirName} is not currently part of this workspace')
+ return
+ endif
+
+ # Notification: "workspace/didChangeWorkspaceFolders"
+ # Param: DidChangeWorkspaceFoldersParams
+ var params = {event: {added: [], removed: [dirName]}}
+ lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
+
+ lspserver.workspaceFolders->remove(idx)
+enddef
+
+def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
+ lspserver.decodeRange(bnr, selRange.range)
+ if selRange->has_key('parent')
+ DecodeSelectionRange(lspserver, bnr, selRange.parent)
+ endif
+enddef
+
+# select the text around the current cursor location
+# Request: "textDocument/selectionRange"
+# Param: SelectionRangeParams
+def SelectionRange(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.isSelectionRangeProvider
+ util.ErrMsg('LSP server does not support selection ranges')
+ return
+ endif
+
+ # clear the previous selection reply
+ lspserver.selection = {}
+
+ # interface SelectionRangeParams
+ # interface TextDocumentIdentifier
+ var param = {
+ textDocument: {
+ uri: util.LspFileToUri(fname)
+ },
+ positions: [lspserver.getPosition(false)]
+ }
+ var reply = lspserver.rpc('textDocument/selectionRange', param)
+
+ if reply->empty() || reply.result->empty()
+ return
+ endif
+
+ # Decode the position encoding in all the selection range items
+ if lspserver.needOffsetEncoding
+ var bnr = fname->bufnr()
+ reply.result->map((_, selItem) => {
+ DecodeSelectionRange(lspserver, bnr, selItem)
+ return selItem
+ })
+ endif
+
+ selection.SelectionStart(lspserver, reply.result)
+enddef
+
+# Expand the previous selection or start a new one
+def SelectionExpand(lspserver: dict<any>)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.isSelectionRangeProvider
+ util.ErrMsg('LSP server does not support selection ranges')
+ return
+ endif
+
+ selection.SelectionModify(lspserver, true)
+enddef
+
+# Shrink the previous selection or start a new one
+def SelectionShrink(lspserver: dict<any>)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.isSelectionRangeProvider
+ util.ErrMsg('LSP server does not support selection ranges')
+ return
+ endif
+
+ selection.SelectionModify(lspserver, false)
+enddef
+
+# fold the entire document
+# Request: "textDocument/foldingRange"
+# Param: FoldingRangeParams
+def FoldRange(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports fold ranges
+ if !lspserver.isFoldingRangeProvider
+ util.ErrMsg('LSP server does not support folding')
+ return
+ endif
+
+ # interface FoldingRangeParams
+ # interface TextDocumentIdentifier
+ var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+ var reply = lspserver.rpc('textDocument/foldingRange', params)
+ if reply->empty() || reply.result->empty()
+ return
+ endif
+
+ # result: FoldingRange[]
+ var end_lnum: number
+ var last_lnum: number = line('$')
+ for foldRange in reply.result
+ end_lnum = foldRange.endLine + 1
+ if end_lnum < foldRange.startLine + 2
+ end_lnum = foldRange.startLine + 2
+ endif
+ exe $':{foldRange.startLine + 2}, {end_lnum}fold'
+ # Open all the folds, otherwise the subsequently created folds are not
+ # correct.
+ :silent! foldopen!
+ endfor
+
+ if &foldcolumn == 0
+ :setlocal foldcolumn=2
+ endif
+enddef
+
+# process the 'workspace/executeCommand' reply from the LSP server
+# Result: any | null
+def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
+ # Nothing to do for the reply
+enddef
+
+# Request the LSP server to execute a command
+# Request: workspace/executeCommand
+# Params: ExecuteCommandParams
+def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
+ # Need to check for lspserver.caps.executeCommandProvider?
+ var params: dict<any> = {}
+ params.command = cmd.command
+ if cmd->has_key('arguments')
+ params.arguments = cmd.arguments
+ endif
+
+ lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
+enddef
+
+# Display the LSP server capabilities (received during the initialization
+# stage).
+def GetCapabilities(lspserver: dict<any>): list<string>
+ var l = []
+ var heading = $"'{lspserver.path}' Language Server Capabilities"
+ var underlines = repeat('=', heading->len())
+ l->extend([heading, underlines])
+ for k in lspserver.caps->keys()->sort()
+ l->add($'{k}: {lspserver.caps[k]->string()}')
+ endfor
+ return l
+enddef
+
+# Display the LSP server initialize request and result
+def GetInitializeRequest(lspserver: dict<any>): list<string>
+ var l = []
+ var heading = $"'{lspserver.path}' Language Server Initialize Request"
+ var underlines = repeat('=', heading->len())
+ l->extend([heading, underlines])
+ if lspserver->has_key('rpcInitializeRequest')
+ for k in lspserver.rpcInitializeRequest->keys()->sort()
+ l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
+ endfor
+ endif
+ return l
+enddef
+
+# Store a log or trace message received from the language server.
+def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
+ # A single message may contain multiple lines separate by newline
+ var msgs = newMsg->split("\n")
+ lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
+ lspserver.messages->extend(msgs[1 : ])
+ # Keep only the last 500 messages to reduce the memory usage
+ if lspserver.messages->len() >= 600
+ lspserver.messages = lspserver.messages[-500 : ]
+ endif
+enddef
+
+# Display the log messages received from the LSP server (window/logMessage)
+def GetMessages(lspserver: dict<any>): list<string>
+ if lspserver.messages->empty()
+ return [$'No messages received from "{lspserver.name}" server']
+ endif
+
+ var l = []
+ var heading = $"'{lspserver.path}' Language Server Messages"
+ var underlines = repeat('=', heading->len())
+ l->extend([heading, underlines])
+ l->extend(lspserver.messages)
+ return l
+enddef
+
+# Send a 'textDocument/definition' request to the LSP server to get the
+# location where the symbol under the cursor is defined and return a list of
+# Dicts in a format accepted by the 'tagfunc' option.
+# Returns null if the LSP server doesn't support getting the location of a
+# symbol definition or the symbol is not defined.
+def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
+ # Check whether LSP server supports getting the location of a definition
+ if !lspserver.isDefinitionProvider
+ return null
+ endif
+
+ # interface DefinitionParams
+ # interface TextDocumentPositionParams
+ var reply = lspserver.rpc('textDocument/definition',
+ lspserver.getTextDocPosition(false))
+ if reply->empty() || reply.result->empty()
+ return null
+ endif
+
+ var taglocations: list<dict<any>>
+ if reply.result->type() == v:t_list
+ taglocations = reply.result
+ else
+ taglocations = [reply.result]
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the reference locations
+ taglocations->map((_, loc) => {
+ lspserver.decodeLocation(loc)
+ return loc
+ })
+ endif
+
+ return symbol.TagFunc(lspserver, taglocations, pat)
+enddef
+
+# Returns unique ID used for identifying the various servers
+var UniqueServerIdCounter = 0
+def GetUniqueServerId(): number
+ UniqueServerIdCounter = UniqueServerIdCounter + 1
+ return UniqueServerIdCounter
+enddef
+
+export def NewLspServer(serverParams: dict<any>): dict<any>
+ var lspserver: dict<any> = {
+ id: GetUniqueServerId(),
+ name: serverParams.name,
+ path: serverParams.path,
+ args: serverParams.args->deepcopy(),
+ running: false,
+ ready: false,
+ job: v:none,
+ data: '',
+ nextID: 1,
+ caps: {},
+ requests: {},
+ callHierarchyType: '',
+ completionTriggerChars: [],
+ customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
+ customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
+ debug: serverParams.debug,
+ features: serverParams.features->deepcopy(),
+ forceOffsetEncoding: serverParams.forceOffsetEncoding,
+ initializationOptions: serverParams.initializationOptions->deepcopy(),
+ messages: [],
+ needOffsetEncoding: false,
+ omniCompletePending: false,
+ peekSymbolFilePopup: -1,
+ peekSymbolPopup: -1,
+ processDiagHandler: serverParams.processDiagHandler,
+ rootSearchFiles: serverParams.rootSearch->deepcopy(),
+ runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
+ runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
+ selection: {},
+ signaturePopup: -1,
+ syncInit: serverParams.syncInit,
+ traceLevel: serverParams.traceLevel,
+ typeHierFilePopup: -1,
+ typeHierPopup: -1,
+ workspaceConfig: serverParams.workspaceConfig->deepcopy(),
+ workspaceSymbolPopup: -1,
+ workspaceSymbolQuery: ''
+ }
+ lspserver.logfile = $'lsp-{lspserver.name}.log'
+ lspserver.errfile = $'lsp-{lspserver.name}.err'
+
+ # Add the LSP server functions
+ lspserver->extend({
+ startServer: function(StartServer, [lspserver]),
+ initServer: function(InitServer, [lspserver]),
+ stopServer: function(StopServer, [lspserver]),
+ shutdownServer: function(ShutdownServer, [lspserver]),
+ exitServer: function(ExitServer, [lspserver]),
+ setTrace: function(SetTrace, [lspserver]),
+ traceLog: function(TraceLog, [lspserver]),
+ errorLog: function(ErrorLog, [lspserver]),
+ nextReqID: function(NextReqID, [lspserver]),
+ createRequest: function(CreateRequest, [lspserver]),
+ createResponse: function(CreateResponse, [lspserver]),
+ sendResponse: function(SendResponse, [lspserver]),
+ sendMessage: function(SendMessage, [lspserver]),
+ sendNotification: function(SendNotification, [lspserver]),
+ rpc: function(Rpc, [lspserver]),
+ rpc_a: function(AsyncRpc, [lspserver]),
+ waitForResponse: function(WaitForResponse, [lspserver]),
+ processReply: function(handlers.ProcessReply, [lspserver]),
+ processNotif: function(handlers.ProcessNotif, [lspserver]),
+ processRequest: function(handlers.ProcessRequest, [lspserver]),
+ processMessages: function(handlers.ProcessMessages, [lspserver]),
+ encodePosition: function(offset.EncodePosition, [lspserver]),
+ decodePosition: function(offset.DecodePosition, [lspserver]),
+ encodeRange: function(offset.EncodeRange, [lspserver]),
+ decodeRange: function(offset.DecodeRange, [lspserver]),
+ encodeLocation: function(offset.EncodeLocation, [lspserver]),
+ decodeLocation: function(offset.DecodeLocation, [lspserver]),
+ getPosition: function(GetPosition, [lspserver]),
+ getTextDocPosition: function(GetTextDocPosition, [lspserver]),
+ featureEnabled: function(FeatureEnabled, [lspserver]),
+ textdocDidOpen: function(TextdocDidOpen, [lspserver]),
+ textdocDidClose: function(TextdocDidClose, [lspserver]),
+ textdocDidChange: function(TextdocDidChange, [lspserver]),
+ sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
+ sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
+ getCompletion: function(GetCompletion, [lspserver]),
+ resolveCompletion: function(ResolveCompletion, [lspserver]),
+ gotoDefinition: function(GotoDefinition, [lspserver]),
+ gotoDeclaration: function(GotoDeclaration, [lspserver]),
+ gotoTypeDef: function(GotoTypeDef, [lspserver]),
+ gotoImplementation: function(GotoImplementation, [lspserver]),
+ tagFunc: function(TagFunc, [lspserver]),
+ switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
+ showSignature: function(ShowSignature, [lspserver]),
+ didSaveFile: function(DidSaveFile, [lspserver]),
+ hover: function(ShowHoverInfo, [lspserver]),
+ showReferences: function(ShowReferences, [lspserver]),
+ docHighlight: function(DocHighlight, [lspserver]),
+ getDocSymbols: function(GetDocSymbols, [lspserver]),
+ textDocFormat: function(TextDocFormat, [lspserver]),
+ prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
+ incomingCalls: function(IncomingCalls, [lspserver]),
+ getIncomingCalls: function(GetIncomingCalls, [lspserver]),
+ outgoingCalls: function(OutgoingCalls, [lspserver]),
+ getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
+ inlayHintsShow: function(InlayHintsShow, [lspserver]),
+ typeHierarchy: function(TypeHierarchy, [lspserver]),
+ renameSymbol: function(RenameSymbol, [lspserver]),
+ codeAction: function(CodeAction, [lspserver]),
+ codeLens: function(CodeLens, [lspserver]),
+ resolveCodeLens: function(ResolveCodeLens, [lspserver]),
+ workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
+ addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
+ removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
+ selectionRange: function(SelectionRange, [lspserver]),
+ selectionExpand: function(SelectionExpand, [lspserver]),
+ selectionShrink: function(SelectionShrink, [lspserver]),
+ foldRange: function(FoldRange, [lspserver]),
+ executeCommand: function(ExecuteCommand, [lspserver]),
+ workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
+ semanticHighlightUpdate: function(SemanticHighlightUpdate, [lspserver]),
+ getCapabilities: function(GetCapabilities, [lspserver]),
+ getInitializeRequest: function(GetInitializeRequest, [lspserver]),
+ addMessage: function(AddMessage, [lspserver]),
+ getMessages: function(GetMessages, [lspserver])
+ })
+
+ return lspserver
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim
new file mode 100644
index 0000000..fd73cee
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim
@@ -0,0 +1,757 @@
+vim9script
+
+# Markdown parser
+# Refer to https://github.github.com/gfm/
+# for the GitHub Flavored Markdown specification.
+
+# TODO: different highlight for different heading level
+# TODO: links
+# TODO: pretty table
+
+
+# Container blocks
+var block_quote = '^ \{,3\}\zs> \='
+var list_marker = '[-+*]\|[0-9]\{1,9}[.)]'
+var list_item = $'^\%({list_marker}\)\ze\s*$\|^ \{{,3}}\zs\%({list_marker}\) \{{1,4}}\ze\S\|^ \{{,3}}\zs\%({list_marker}\) \{{5}}\ze\s*\S'
+# pattern to match list items
+export var list_pattern = $'^ *\%({list_marker}\) *'
+
+
+# Leaf blocks
+var blank_line = '^\s*$'
+var thematic_break = '^ \{,3\}\([-_*]\)\%(\s*\1\)\{2,\}\s*$'
+var code_fence = '^ \{,3\}\(`\{3,\}\|\~\{3,\}\)\s*\(\S*\)'
+var code_indent = '^ \{4\}\zs\s*\S.*'
+var paragraph = '^\s*\zs\S.\{-}\s*\ze$'
+
+var atx_heading = '^ \{,3}\zs\(#\{1,6}\) \s*\(.\{-}\)\s*\ze\%( #\{1,}\s*\)\=$'
+var setext_heading = '^ \{,3}\zs\%(=\{1,}\|-\{1,}\)\ze *$'
+var setext_heading_level = {"=": 1, "-": 2}
+
+var table_delimiter = '^|\=\zs *:\=-\{1,}:\= *\%(| *:\=-\{1,}:\= *\)*\ze|\=$'
+
+var punctuation = "[!\"#$%&'()*+,-./:;<=>?@[\\\\\\\]^_`{|}~]"
+
+# Setting text properties
+highlight LspBold term=bold cterm=bold gui=bold
+highlight LspItalic term=italic cterm=italic gui=italic
+highlight LspStrikeThrough term=strikethrough cterm=strikethrough gui=strikethrough
+prop_type_add('LspMarkdownBold', {highlight: 'LspBold'})
+prop_type_add('LspMarkdownItalic', {highlight: 'LspItalic'})
+prop_type_add('LspMarkdownStrikeThrough', {highlight: 'LspStrikeThrough'})
+prop_type_add('LspMarkdownHeading', {highlight: 'Function'})
+prop_type_add('LspMarkdownCode', {highlight: 'PreProc'})
+prop_type_add('LspMarkdownCodeBlock', {highlight: 'PreProc'})
+prop_type_add('LspMarkdownListMarker', {highlight: 'Special'})
+prop_type_add('LspMarkdownTableHeader', {highlight: 'Label'})
+prop_type_add('LspMarkdownTableMarker', {highlight: 'Special'})
+
+
+def GetMarkerProp(marker: string, col: number, ...opt: list<any>): dict<any>
+ if marker == 'list_item'
+ return {
+ type: 'LspMarkdownListMarker',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'code_block'
+ return {
+ type: 'LspMarkdownCodeBlock',
+ col: col,
+ end_lnum: opt[0],
+ end_col: opt[1]
+ }
+ elseif marker == 'heading'
+ return {
+ type: 'LspMarkdownHeading',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'table_header'
+ return {
+ type: 'LspMarkdownTableHeader',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'table_sep'
+ return {
+ type: 'LspMarkdownTableMarker',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'code_span'
+ return {
+ type: 'LspMarkdownCode',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'emphasis'
+ return {
+ type: 'LspMarkdownItalic',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'strong'
+ return {
+ type: 'LspMarkdownBold',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'strikethrough'
+ return {
+ type: 'LspMarkdownStrikeThrough',
+ col: col,
+ length: opt[0]
+ }
+ endif
+ return {}
+enddef
+
+def GetCodeSpans(text: string): list<dict<any>>
+ var code_spans = []
+ var pos = 0
+ while pos < text->len()
+ var backtick = text->matchstrpos('\\*`', pos)
+ if backtick[1] < 0
+ break
+ endif
+ if backtick[0]->len() % 2 == 0
+ # escaped backtick
+ pos = backtick[2]
+ continue
+ endif
+ pos = backtick[2] - 1
+ var code_span = text->matchstrpos('^\(`\+\)`\@!.\{-}`\@1<!\1`\@!', pos)
+ if code_span[1] < 0
+ break
+ endif
+ var code_text = text->matchstrpos('^\(`\+\)\%(\zs \+\ze\|\([ \n]\=\)\zs.\{-}\S.\{-}\ze\2\)`\@1<!\1`\@!', pos)
+ code_spans->add({
+ marker: '`',
+ start: [code_span[1], code_text[1]],
+ end: [code_text[2], code_span[2]]
+ })
+ pos = code_span[2]
+ endwhile
+ return code_spans
+enddef
+
+def Unescape(text: string, block_marker: string = ""): string
+ if block_marker == '`'
+ # line breaks do not occur inside code spans
+ return text->substitute('\n', ' ', 'g')
+ endif
+ # use 2 spaces instead of \ for hard line break
+ var result = text->substitute('\\\@<!\(\(\\\\\)*\)\\\n', '\1 \n', 'g')
+ # change soft line breaks
+ result = result->substitute(' \@<! \=\n', ' ', 'g')
+ # change hard line breaks
+ result = result->substitute(' \{2,}\n', '\n', 'g')
+ # replace non-breaking spaces with spaces
+ result = result->substitute('&nbsp;', ' ', 'g')
+ return result->substitute($'\\\({punctuation}\)', '\1', 'g')
+enddef
+
+def GetNextInlineDelimiter(text: string, start_pos: number, end_pos: number): dict<any>
+ var pos = start_pos
+ while pos < text->len()
+ # search the first delimiter char
+ var delimiter = text->matchstrpos('\\*[_*~]', pos)
+ if delimiter[1] < 0 || delimiter[1] > end_pos
+ return {}
+ endif
+ if delimiter[0]->len() % 2 == 0
+ # escaped delimiter char
+ pos = delimiter[2]
+ continue
+ endif
+ pos = delimiter[2] - 1
+ var delimiter_run = text->matchstrpos(
+ $'{delimiter[0][-1]->substitute("\\([*~]\\)", "\\\\\\1", "g")}\+',
+ pos)
+ if delimiter_run[0][0] == '~' && delimiter_run[0]->len() > 2
+ pos = delimiter_run[2]
+ continue
+ endif
+ var add_char = ''
+ if pos > 0
+ pos -= 1
+ add_char = '.'
+ endif
+ var delim_regex = delimiter_run[0]->substitute('\([*~]\)', '\\\1', 'g')
+ var is_left = text->match($'^{add_char}{delim_regex}\%(\s\|$\|{punctuation}\)\@!\|^{add_char}\%(\s\|^\|{punctuation}\)\@1<={delim_regex}{punctuation}', pos) >= 0
+ var is_right = text->match($'^{add_char}\%(\s\|^\|{punctuation}\)\@1<!{delim_regex}\|^{add_char}{punctuation}\@1<={delim_regex}\%(\s\|$\|{punctuation}\)', pos) >= 0
+ if !is_left && ! is_right
+ pos = delimiter_run[2]
+ continue
+ endif
+ if delimiter_run[0][0] == '_'
+ && text->match($'^\w{delimiter_run[0]}\w', pos) >= 0
+ # intraword emphasis is disallowed
+ pos = delimiter_run[2]
+ continue
+ endif
+ return {
+ marker: delimiter_run[0],
+ start: [delimiter_run[1], delimiter_run[2]],
+ left: is_left,
+ right: is_right
+ }
+ endwhile
+ return {}
+enddef
+
+def GetNextInlineBlock(text: string, blocks: list<any>, rel_pos: number): dict<any>
+ var result = {
+ text: '',
+ props: []
+ }
+ var cur = blocks->remove(0)
+ var pos = cur.start[1]
+ while blocks->len() > 0 && cur.end[0] >= blocks[0].start[0]
+ result.text ..= Unescape(text->strpart(pos, blocks[0].start[0] - pos), cur.marker[0])
+ # get nested block
+ var part = GetNextInlineBlock(text, blocks, rel_pos + result.text->len())
+ result.text ..= part.text
+ result.props += part.props
+ pos = part.end_pos
+ endwhile
+ result.text ..= Unescape(text->strpart(pos, cur.end[0] - pos), cur.marker[0])
+ # add props for current inline block
+ var prop_type = {
+ '`': 'code_span',
+ '_': 'emphasis',
+ '__': 'strong',
+ '*': 'emphasis',
+ '**': 'strong',
+ '~': 'strikethrough',
+ '~~': 'strikethrough'
+ }
+ result.props->insert(GetMarkerProp(prop_type[cur.marker],
+ rel_pos + 1,
+ result.text->len()))
+ result->extend({'end_pos': cur.end[1]})
+ return result
+enddef
+
+def ParseInlines(text: string, rel_pos: number = 0): dict<any>
+ var formatted = {
+ text: '',
+ props: []
+ }
+ var code_spans = GetCodeSpans(text)
+
+ var pos = 0
+ var seq = []
+ # search all emphasis
+ while pos < text->len()
+ var code_pos: list<number>
+ if code_spans->len() > 0
+ code_pos = [code_spans[0].start[0], code_spans[0].end[1]]
+ if pos >= code_pos[0]
+ pos = code_pos[1]
+ seq->add(code_spans->remove(0))
+ continue
+ endif
+ else
+ code_pos = [text->len(), text->len()]
+ endif
+ var delimiter = GetNextInlineDelimiter(text, pos, code_pos[0])
+ if delimiter->empty()
+ pos = code_pos[1]
+ continue
+ endif
+ if delimiter.right
+ var idx = seq->len() - 1
+ while idx >= 0
+ if delimiter.marker[0] != seq[idx].marker[0]
+ || seq[idx]->has_key('end')
+ idx -= 1
+ continue
+ endif
+ if delimiter.left || seq[idx].right
+ # check the sum rule
+ if (delimiter.marker->len() + seq[idx].marker->len()) % 3 == 0
+ && (delimiter.marker->len() % 3 > 0
+ || seq[idx].marker->len() % 3 > 0)
+ # not valid condition
+ idx -= 1
+ continue
+ endif
+ endif
+ var marker_len = min([delimiter.marker->len(),
+ seq[idx].marker->len(), 2])
+ if seq[idx].marker->len() > marker_len
+ var new_delim = {
+ marker: delimiter.marker[0]->repeat(marker_len),
+ start: [seq[idx].start[1] - marker_len, seq[idx].start[1]],
+ left: true,
+ right: false
+ }
+ seq[idx].marker = seq[idx].marker[: -1 - marker_len]
+ seq[idx].start[1] -= marker_len
+ seq[idx].right = false
+ idx += 1
+ seq->insert(new_delim, idx)
+ endif
+ seq[idx]->extend({
+ end: [delimiter.start[0],
+ delimiter.start[0] + marker_len]})
+ # close all overlapped emphasis spans not closed
+ for i in range(seq->len() - 1, idx + 1, -1)
+ if !seq[i]->has_key('end')
+ seq->remove(i)
+ endif
+ endfor
+ if delimiter.marker->len() > marker_len
+ delimiter.start[0] += marker_len
+ else
+ delimiter.left = false
+ break
+ endif
+ idx -= 1
+ endwhile
+ endif
+ if delimiter.left
+ seq->add(delimiter)
+ endif
+ pos = delimiter.start[1]
+ endwhile
+ while code_spans->len() > 0
+ seq->add(code_spans->remove(0))
+ endwhile
+ # remove all not closed delimiters
+ for i in range(seq->len() - 1, 0, -1)
+ if !seq[i]->has_key('end')
+ seq->remove(i)
+ endif
+ endfor
+
+ # compose final text
+ pos = 0
+ while seq->len() > 0
+ if pos < seq[0].start[0]
+ formatted.text ..= Unescape(text->strpart(pos, seq[0].start[0] - pos))
+ pos = seq[0].start[0]
+ endif
+ var inline = GetNextInlineBlock(text, seq,
+ rel_pos + formatted.text->len())
+ formatted.text ..= inline.text
+ formatted.props += inline.props
+ pos = inline.end_pos
+ endwhile
+ if pos < text->len()
+ formatted.text ..= Unescape(text->strpart(pos))
+ endif
+ return formatted
+enddef
+
+# new open container block
+def CreateContainerBlock(match: list<any>, start_lnum: number): dict<any>
+ if match[0][0] == '>'
+ return {
+ type: 'quote_block',
+ lnum: start_lnum,
+ indent: 0
+ }
+ else
+ return {
+ type: 'list_item',
+ lnum: start_lnum,
+ marker: $' {match[0]->matchstr("\\S\\+")} ',
+ indent: match[2]
+ }
+ endif
+enddef
+
+# new open leaf block
+def CreateLeafBlock(block_type: string, line: string, ...opt: list<any>): dict<any>
+ if block_type == 'fenced_code'
+ var token = line->matchlist(code_fence)
+ return {
+ type: block_type,
+ fence: token[1],
+ language: token[2],
+ text: []
+ }
+ elseif block_type == 'indented_code'
+ return {
+ type: block_type,
+ text: [line->matchstr(code_indent)]
+ }
+ elseif block_type == 'paragraph'
+ return {
+ type: block_type,
+ text: [line->matchstr(paragraph)]
+ }
+ elseif block_type == 'heading'
+ return {
+ type: block_type,
+ level: opt[0],
+ text: line
+ }
+ elseif block_type == 'table'
+ return {
+ type: block_type,
+ header: line,
+ delimiter: opt[0],
+ text: []
+ }
+ endif
+ return {}
+enddef
+
+def NeedBlankLine(prev: string, cur: string): bool
+ if prev == 'hr' || cur == 'hr'
+ return false
+ elseif prev == 'heading' || cur == 'heading'
+ return true
+ elseif prev == 'paragraph' && cur == 'paragraph'
+ return true
+ elseif prev != cur
+ return true
+ endif
+ return false
+enddef
+
+def SplitLine(line: dict<any>, indent: number = 0): list<dict<any>>
+ var lines: list<dict<any>> = []
+ var tokens: list<string> = line.text->split("\n", true)
+ if tokens->len() == 1
+ lines->add(line)
+ return lines
+ endif
+ var props: list<dict<any>> = line.props
+ for cur_text in tokens
+ var cur_props: list<dict<any>> = []
+ var next_props: list<dict<any>> = []
+ var length: number = cur_text->len()
+ for prop in props
+ if prop.col + prop.length - 1 <= length
+ cur_props->add(prop)
+ elseif prop.col > length
+ prop.col -= length + 1
+ next_props->add(prop)
+ else
+ var cur_length: number = length - prop.col + 1
+ cur_props->add({
+ type: prop.type,
+ col: prop.col,
+ length: cur_length
+ })
+ prop.col = 1
+ prop.length -= cur_length + 1
+ next_props->add(prop)
+ endif
+ endfor
+ lines->add({
+ text: cur_text,
+ props: cur_props
+ })
+ props = next_props
+ endfor
+ return lines
+enddef
+
+var last_block: string = ''
+
+def CloseBlocks(document: dict<list<any>>, blocks: list<dict<any>>, start: number = 0): void
+ if start >= blocks->len()
+ return
+ endif
+ var line: dict<any> = {
+ text: '',
+ props: []
+ }
+ if !document.content->empty() && NeedBlankLine(last_block, blocks[0].type)
+ document.content->add({text: '', props: []})
+ endif
+ last_block = blocks[0].type
+
+ for i in start->range()
+ if blocks[i]->has_key('marker')
+ if blocks[i].marker =~ '\S'
+ line.props->add(GetMarkerProp('list_item',
+ line.text->len() + 1,
+ blocks[i].marker->len()))
+ line.text ..= blocks[i].marker
+ blocks[i].marker = ' '->repeat(blocks[i].marker->len())
+ else
+ line.text ..= blocks[i].marker
+ endif
+ endif
+ endfor
+ for block in blocks->remove(start, -1)
+ if block.type =~ 'quote_block\|list_item'
+ if block->has_key('marker')
+ if block.marker =~ '\S'
+ line.props->add(GetMarkerProp('list_item',
+ line.text->len() + 1,
+ block.marker->len()))
+ line.text ..= block.marker
+ block.marker = ' '->repeat(block.marker->len())
+ else
+ line.text ..= block.marker
+ endif
+ endif
+ else
+ # leaf block
+ if block.type =~ '_code'
+ if block.type == 'indented_code'
+ while !block.text->empty() && block.text[0] !~ '\S'
+ block.text->remove(0)
+ endwhile
+ while !block.text->empty() && block.text[-1] !~ '\S'
+ block.text->remove(-1)
+ endwhile
+ endif
+ if !block.text->empty()
+ var indent = ' '->repeat(line.text->len())
+ var max_len = mapnew(block.text, (_, l) => l->len())->max()
+ var text = block.text->remove(0)
+ line.text ..= text
+ document.content->add(line)
+ var startline = document.content->len()
+ for l in block.text
+ document.content->add({text: indent .. l})
+ endfor
+ if block->has_key('language')
+ && !globpath(&rtp, $'syntax/{block.language}.vim')->empty()
+ document.syntax->add({lang: block.language,
+ start: $'\%{startline}l\%{indent->len() + 1}c',
+ end: $'\%{document.content->len()}l$'})
+ else
+ line.props->add(GetMarkerProp('code_block',
+ indent->len() + 1,
+ document.content->len(),
+ indent->len() + max_len + 1))
+ endif
+ endif
+ elseif block.type == 'heading'
+ line.props->add(GetMarkerProp('heading',
+ line.text->len() + 1,
+ block.text->len(),
+ block.level))
+ var format = ParseInlines(block.text, line.text->len())
+ line.text ..= format.text
+ line.props += format.props
+ document.content += SplitLine(line)
+ elseif block.type == 'table'
+ var indent = line.text
+ var head = block.header->split('\\\@1<!|')
+ var col1 = head->remove(0)
+ var format = ParseInlines(col1, line.text->len())
+ line.props->add(GetMarkerProp('table_header',
+ line.text->len() + 1,
+ format.text->len()))
+ line.text ..= format.text
+ line.props += format.props
+ for colx in head
+ format = ParseInlines(colx, line.text->len() + 1)
+ line.props->add(GetMarkerProp('table_sep', line.text->len() + 1, 1))
+ line.props->add(GetMarkerProp('table_header',
+ line.text->len() + 2,
+ format.text->len()))
+ line.text ..= $'|{format.text}'
+ line.props += format.props
+ endfor
+ document.content->add(line)
+ var data = {
+ text: indent .. block.delimiter,
+ props: [GetMarkerProp('table_sep',
+ indent->len() + 1,
+ block.delimiter->len())]
+ }
+ document.content->add(data)
+ for row in block.text
+ data = {
+ text: indent,
+ props: []
+ }
+ var cell = row->split('\\\@1<!|')
+ col1 = cell->remove(0)
+ format = ParseInlines(col1, data.text->len())
+ data.text ..= format.text
+ data.props += format.props
+ for colx in cell
+ format = ParseInlines(colx, data.text->len() + 1)
+ data.props->add(GetMarkerProp('table_sep',
+ data.text->len() + 1,
+ 1))
+ data.text ..= $'|{format.text}'
+ data.props += format.props
+ endfor
+ document.content->add(data)
+ endfor
+ elseif block.type == 'paragraph'
+ var indent = line.text->len()
+ var format = ParseInlines(block.text->join("\n")->substitute('\s\+$', '', ''), indent)
+ line.text ..= format.text
+ line.props += format.props
+ document.content += SplitLine(line, indent)
+ endif
+ endif
+ endfor
+enddef
+
+def ExpandTabs(line: string): string
+ var block_marker = line->matchstrpos($'^ \{{,3}}>[ \t]\+\|^[ \t]*\%({list_marker}\)\=[ \t]*')
+ if block_marker[0]->match('\t') < 0
+ return line
+ endif
+ var begin: string = ""
+ for char in block_marker[0]
+ if char == ' '
+ begin ..= ' '->repeat(4 - (begin->len() % 4))
+ else
+ begin ..= char
+ endif
+ endfor
+ return begin .. line[block_marker[2] :]
+enddef
+
+export def ParseMarkdown(data: list<string>, width: number = 80): dict<list<any>>
+ var document: dict<list<any>> = {content: [], syntax: []}
+ var open_blocks: list<dict<any>> = []
+
+ for l in data
+ var line: string = ExpandTabs(l)
+ var cur = 0
+
+ # for each open block check if current line continue it
+ while cur < open_blocks->len()
+ if open_blocks[cur].type == 'quote_block'
+ var marker = line->matchstrpos(block_quote)
+ if marker[1] == -1
+ break
+ endif
+ line = line->strpart(marker[2])
+ elseif open_blocks[cur].type == 'list_item'
+ var marker = line->matchstrpos($'^ \{{{open_blocks[cur].indent}}}')
+ if marker[1] == -1
+ break
+ endif
+ line = line->strpart(marker[2])
+ elseif open_blocks[cur].type == 'fenced_code'
+ if line =~ $'^ \{{,3}}{open_blocks[cur].fence}{open_blocks[cur].fence[0]}* *$'
+ CloseBlocks(document, open_blocks, cur)
+ else
+ open_blocks[cur].text->add(line)
+ endif
+ cur = -1
+ break
+ elseif open_blocks[cur].type == 'indented_code'
+ var marker = line->matchstrpos(code_indent)
+ if marker[1] >= 0
+ open_blocks[cur].text->add(marker[0])
+ cur = -1
+ endif
+ break
+ elseif open_blocks[cur].type == 'paragraph'
+ if line =~ setext_heading
+ var marker = line->matchstrpos(setext_heading)
+ open_blocks->add(CreateLeafBlock(
+ 'heading',
+ open_blocks->remove(cur).text->join("\n")->substitute('\s\+$', '', ''),
+ setext_heading_level[marker[0][0]]))
+ CloseBlocks(document, open_blocks, cur)
+ cur = -1
+ elseif open_blocks[cur].text->len() == 1
+ # may be a table
+ var marker = line->matchstr(table_delimiter)
+ if !marker->empty()
+ if open_blocks[cur].text[0]->split('\\\@1<!|')->len() == marker->split('|')->len()
+ open_blocks->add(CreateLeafBlock(
+ 'table',
+ open_blocks->remove(cur).text[0],
+ marker))
+ cur = -1
+ endif
+ endif
+ endif
+ break
+ endif
+ cur += 1
+ endwhile
+
+ if cur < 0
+ # the whole line is already consumed
+ continue
+ endif
+
+ # a thematic break close all previous blocks
+ if line =~ thematic_break
+ CloseBlocks(document, open_blocks)
+ if &g:encoding == 'utf-8'
+ document.content->add({text: "\u2500"->repeat(width)})
+ else
+ document.content->add({text: '-'->repeat(width)})
+ endif
+ last_block = 'hr'
+ continue
+ endif
+
+ # check for new container blocks
+ while true
+ var block = line->matchstrpos($'{block_quote}\|{list_item}')
+ if block[1] < 0
+ break
+ endif
+ # close unmatched blocks
+ CloseBlocks(document, open_blocks, cur)
+ # start a new block
+ open_blocks->add(CreateContainerBlock(block, document->len()))
+ cur = open_blocks->len()
+ line = line->strpart(block[2])
+ endwhile
+
+ # check for leaf block
+ if line =~ code_fence
+ CloseBlocks(document, open_blocks, cur)
+ open_blocks->add(CreateLeafBlock('fenced_code', line))
+ elseif line =~ blank_line
+ if open_blocks->empty()
+ continue
+ endif
+ if open_blocks[-1].type == 'paragraph'
+ CloseBlocks(document, open_blocks, min([cur, open_blocks->len() - 1]))
+ elseif open_blocks[-1].type == 'table'
+ CloseBlocks(document, open_blocks, open_blocks->len() - 1)
+ elseif open_blocks[-1].type =~ '_code'
+ open_blocks[-1].text->add(line)
+ endif
+ elseif line =~ code_indent
+ if open_blocks->empty()
+ open_blocks->add(CreateLeafBlock('indented_code', line))
+ elseif open_blocks[-1].type =~ '_code'
+ open_blocks[-1].text->add(line->matchstr(code_indent))
+ elseif open_blocks[-1].type == 'paragraph'
+ open_blocks[-1].text->add(line->matchstr(paragraph))
+ else
+ CloseBlocks(document, open_blocks, cur)
+ open_blocks->add(CreateLeafBlock('indented_code', line))
+ endif
+ elseif line =~ atx_heading
+ CloseBlocks(document, open_blocks, cur)
+ var token = line->matchlist(atx_heading)
+ open_blocks->add(CreateLeafBlock('heading', token[2], token[1]->len()))
+ CloseBlocks(document, open_blocks, cur)
+ elseif !open_blocks->empty()
+ if open_blocks[-1].type == 'table'
+ open_blocks[-1].text->add(line)
+ elseif open_blocks[-1].type == 'paragraph'
+ open_blocks[-1].text->add(line->matchstr(paragraph))
+ else
+ CloseBlocks(document, open_blocks, cur)
+ open_blocks->add(CreateLeafBlock('paragraph', line))
+ endif
+ else
+ open_blocks->add(CreateLeafBlock('paragraph', line))
+ endif
+ endfor
+
+ CloseBlocks(document, open_blocks)
+ return document
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim
new file mode 100644
index 0000000..699964e
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim
@@ -0,0 +1,168 @@
+vim9script
+
+import './util.vim'
+
+# Functions for encoding and decoding the LSP position offsets. Language
+# servers support either UTF-8 or UTF-16 or UTF-32 position offsets. The
+# character related Vim functions use the UTF-32 position offset. The
+# encoding used is negotiated during the language server initialization.
+
+# Encode the UTF-32 character offset in the LSP position "pos" to the encoding
+# negotiated with the language server.
+#
+# Modifies in-place the UTF-32 offset in pos.character to a UTF-8 or UTF-16 or
+# UTF-32 offset.
+export def EncodePosition(lspserver: dict<any>, bnr: number, pos: dict<number>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32 || bnr <= 0
+ # LSP client plugin also uses utf-32 encoding
+ return
+ endif
+
+ :silent! bnr->bufload()
+ var text = bnr->getbufline(pos.line + 1)->get(0, '')
+ if text->empty()
+ return
+ endif
+
+ if lspserver.posEncoding == 16
+ pos.character = text->utf16idx(pos.character, true, true)
+ else
+ pos.character = text->byteidxcomp(pos.character)
+ endif
+ endif
+enddef
+
+# Decode the character offset in the LSP position "pos" using the encoding
+# negotiated with the language server to a UTF-32 offset.
+#
+# Modifies in-place the UTF-8 or UTF-16 or UTF-32 offset in pos.character to a
+# UTF-32 offset.
+export def DecodePosition(lspserver: dict<any>, bnr: number, pos: dict<number>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32 || bnr <= 0
+ # LSP client plugin also uses utf-32 encoding
+ return
+ endif
+
+ :silent! bnr->bufload()
+ var text = bnr->getbufline(pos.line + 1)->get(0, '')
+ # If the line is empty then don't decode the character position.
+ if text->empty()
+ return
+ endif
+
+ # If the character position is out-of-bounds, then don't decode the
+ # character position.
+ var textLen = 0
+ if lspserver.posEncoding == 16
+ textLen = text->strutf16len(true)
+ else
+ textLen = text->strlen()
+ endif
+
+ if pos.character > textLen
+ return
+ endif
+
+ if pos.character == textLen
+ pos.character = text->strchars()
+ else
+ if lspserver.posEncoding == 16
+ pos.character = text->charidx(pos.character, true, true)
+ else
+ pos.character = text->charidx(pos.character, true)
+ endif
+ endif
+ endif
+enddef
+
+# Encode the start and end UTF-32 character offsets in the LSP range "range"
+# to the encoding negotiated with the language server.
+#
+# Modifies in-place the UTF-32 offset in range.start.character and
+# range.end.character to a UTF-8 or UTF-16 or UTF-32 offset.
+export def EncodeRange(lspserver: dict<any>, bnr: number,
+ range: dict<dict<number>>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ EncodePosition(lspserver, bnr, range.start)
+ EncodePosition(lspserver, bnr, range.end)
+ endif
+enddef
+
+# Decode the start and end character offsets in the LSP range "range" to
+# UTF-32 offsets.
+#
+# Modifies in-place the offset value in range.start.character and
+# range.end.character to a UTF-32 offset.
+export def DecodeRange(lspserver: dict<any>, bnr: number,
+ range: dict<dict<number>>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ DecodePosition(lspserver, bnr, range.start)
+ DecodePosition(lspserver, bnr, range.end)
+ endif
+enddef
+
+# Encode the range in the LSP position "location" to the encoding negotiated
+# with the language server.
+#
+# Modifies in-place the UTF-32 offset in location.range to a UTF-8 or UTF-16
+# or UTF-32 offset.
+export def EncodeLocation(lspserver: dict<any>, location: dict<any>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ var bnr = 0
+ if location->has_key('targetUri')
+ # LocationLink
+ bnr = util.LspUriToBufnr(location.targetUri)
+ if bnr > 0
+ # We use only the "targetSelectionRange" item. The
+ # "originSelectionRange" and the "targetRange" items are not used.
+ lspserver.encodeRange(bnr, location.targetSelectionRange)
+ endif
+ else
+ # Location
+ bnr = util.LspUriToBufnr(location.uri)
+ if bnr > 0
+ lspserver.encodeRange(bnr, location.range)
+ endif
+ endif
+ endif
+enddef
+
+# Decode the range in the LSP location "location" to UTF-32.
+#
+# Modifies in-place the offset value in location.range to a UTF-32 offset.
+export def DecodeLocation(lspserver: dict<any>, location: dict<any>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ var bnr = 0
+ if location->has_key('targetUri')
+ # LocationLink
+ bnr = util.LspUriToBufnr(location.targetUri)
+ # We use only the "targetSelectionRange" item. The
+ # "originSelectionRange" and the "targetRange" items are not used.
+ lspserver.decodeRange(bnr, location.targetSelectionRange)
+ else
+ # Location
+ bnr = util.LspUriToBufnr(location.uri)
+ lspserver.decodeRange(bnr, location.range)
+ endif
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/options.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/options.vim
new file mode 100644
index 0000000..50697a3
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/options.vim
@@ -0,0 +1,179 @@
+vim9script
+
+export const COMPLETIONMATCHER_CASE = 1
+export const COMPLETIONMATCHER_ICASE = 2
+export const COMPLETIONMATCHER_FUZZY = 3
+
+# LSP plugin options
+# User can override these by calling the OptionsSet() function.
+export var lspOptions: dict<any> = {
+ # Enable ale diagnostics support.
+ # If true, diagnostics will be sent to ale, which will be responsible for
+ # showing them.
+ aleSupport: false,
+
+ # In insert mode, complete the current symbol automatically
+ # Otherwise, use omni-completion
+ autoComplete: true,
+
+ # In normal mode, highlight the current symbol automatically
+ autoHighlight: false,
+
+ # Automatically highlight diagnostics messages from LSP server
+ autoHighlightDiags: true,
+
+ # Automatically populate the location list with new diagnostics
+ autoPopulateDiags: false,
+
+ # icase | fuzzy | case match for language servers that replies with a full
+ # list of completion items
+ completionMatcher: 'case',
+
+ # Due to a bug in the earlier versions of Vim, cannot use the
+ # COMPLETIONMATCHER_CASE constant here for initialization.
+ completionMatcherValue: 1,
+
+ # diagnostics signs options
+ diagSignErrorText: 'E>',
+ diagSignHintText: 'H>',
+ diagSignInfoText: 'I>',
+ diagSignWarningText: 'W>',
+
+ # In insert mode, echo the current symbol signature in the status line
+ # instead of showing it in a popup
+ echoSignature: false,
+
+ # hide disabled code actions
+ hideDisabledCodeActions: false,
+
+ # Highlight diagnostics inline
+ highlightDiagInline: true,
+
+ # Show the symbol documentation in the preview window instead of in a popup
+ hoverInPreview: false,
+
+ # Don't print message when a configured language server is missing.
+ ignoreMissingServer: false,
+
+ # Focus on the location list window after ":LspDiag show"
+ keepFocusInDiags: true,
+
+ # Focus on the location list window after LspShowReferences
+ keepFocusInReferences: true,
+
+ # If true, apply the LSP server supplied text edits after a completion.
+ # If a snippet plugin is going to apply the text edits, then set this to
+ # false to avoid applying the text edits twice.
+ completionTextEdit: true,
+
+ # Alignment of virtual diagnostic text, when showDiagWithVirtualText is true
+ # Allowed values: 'above' | 'below' | 'after' (default is 'above')
+ diagVirtualTextAlign: 'above',
+
+ # Wrapping of virtual diagnostic text, when showDiagWithVirtualText is true.
+ # Allowed valuse: 'default' | 'truncate' | 'wrap' (default is 'default')
+ diagVirtualTextWrap: 'default',
+
+ # Suppress adding a new line on completion selection with <CR>
+ noNewlineInCompletion: false,
+
+ # Omni-completion support. To keep backward compatibility, this option is
+ # set to null by default instead of false.
+ omniComplete: null,
+
+ # Open outline window on right side
+ outlineOnRight: false,
+
+ # Outline window size
+ outlineWinSize: 20,
+
+ # Enable semantic highlighting
+ semanticHighlight: false,
+
+ # Show diagnostic text in a balloon when the mouse is over the diagnostic
+ showDiagInBalloon: true,
+
+ # Make diagnostics show in a popup instead of echoing
+ showDiagInPopup: true,
+
+ # Suppress diagnostic hover from appearing when the mouse is over the line
+ # Show a diagnostic message on a status line
+ showDiagOnStatusLine: false,
+
+ # Show a diagnostic messages using signs
+ showDiagWithSign: true,
+
+ # Show a diagnostic messages with virtual text
+ showDiagWithVirtualText: false,
+
+ # enable inlay hints
+ showInlayHints: false,
+
+ # In insert mode, show the current symbol signature automatically
+ showSignature: true,
+
+ # enable snippet completion support
+ snippetSupport: false,
+
+ # enable SirVer/ultisnips completion support
+ ultisnipsSupport: false,
+
+ # add to autocomplition list current buffer words
+ useBufferCompletion: false,
+
+ # Use a floating menu to show the code action menu instead of asking for
+ # input
+ usePopupInCodeAction: false,
+
+ # ShowReferences in a quickfix list instead of a location list`
+ useQuickfixForLocations: false,
+
+ # enable hrsh7th/vim-vsnip completion support
+ vsnipSupport: false,
+
+ # Limit the time autocompletion searches for words in current buffer (in
+ # milliseconds)
+ bufferCompletionTimeout: 100,
+
+ # Enable support for custom completion kinds
+ customCompletionKinds: false,
+
+ # A dictionary with all completion kinds that you want to customize
+ completionKinds: {},
+
+ # Filter duplicate completion items
+ filterCompletionDuplicates: false,
+}
+
+# set the LSP plugin options from the user provided option values
+export def OptionsSet(opts: dict<any>)
+ lspOptions->extend(opts)
+ if !has('patch-9.0.0178')
+ lspOptions.showInlayHints = false
+ endif
+ if !has('patch-9.0.1157')
+ lspOptions.showDiagWithVirtualText = false
+ endif
+
+ # For faster comparison, convert the 'completionMatcher' option value from a
+ # string to a number.
+ if lspOptions.completionMatcher == 'icase'
+ lspOptions.completionMatcherValue = COMPLETIONMATCHER_ICASE
+ elseif lspOptions.completionMatcher == 'fuzzy'
+ lspOptions.completionMatcherValue = COMPLETIONMATCHER_FUZZY
+ else
+ lspOptions.completionMatcherValue = COMPLETIONMATCHER_CASE
+ endif
+
+ # Apply the changed options
+ if exists('#LspCmds#User#LspOptionsChanged')
+ :doautocmd <nomodeline> LspCmds User LspOptionsChanged
+ endif
+enddef
+
+# return a copy of the LSP plugin options
+export def OptionsGet(): dict<any>
+ return lspOptions->deepcopy()
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim
new file mode 100644
index 0000000..b414884
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim
@@ -0,0 +1,304 @@
+vim9script
+
+import './util.vim'
+import './options.vim' as opt
+
+# jump to a symbol selected in the outline window
+def OutlineJumpToSymbol()
+ var lnum: number = line('.') - 1
+ if w:lspSymbols.lnumTable[lnum]->empty()
+ return
+ endif
+
+ var slnum: number = w:lspSymbols.lnumTable[lnum].lnum
+ var scol: number = w:lspSymbols.lnumTable[lnum].col
+ var fname: string = w:lspSymbols.filename
+
+ # Highlight the selected symbol
+ prop_remove({type: 'LspOutlineHighlight'})
+ var col: number = getline('.')->match('\S') + 1
+ prop_add(line('.'), col, {type: 'LspOutlineHighlight',
+ length: w:lspSymbols.lnumTable[lnum].name->len()})
+
+ # disable the outline window refresh
+ skipRefresh = true
+
+ # If the file is already opened in a window, jump to it. Otherwise open it
+ # in another window
+ var wid: number = fname->bufwinid()
+ if wid == -1
+ # Find a window showing a normal buffer and use it
+ for w in getwininfo()
+ if w.winid->getwinvar('&buftype')->empty()
+ wid = w.winid
+ wid->win_gotoid()
+ break
+ endif
+ endfor
+ if wid == -1
+ var symWinid: number = win_getid()
+ :rightbelow vnew
+ # retain the fixed symbol window width
+ win_execute(symWinid, 'vertical resize 20')
+ endif
+
+ exe $'edit {fname}'
+ else
+ wid->win_gotoid()
+ endif
+ [slnum, scol]->cursor()
+ skipRefresh = false
+enddef
+
+# Skip refreshing the outline window. Used to prevent recursive updates to the
+# outline window
+var skipRefresh: bool = false
+
+export def SkipOutlineRefresh(): bool
+ return skipRefresh
+enddef
+
+def AddSymbolText(bnr: number,
+ symbolTypeTable: dict<list<dict<any>>>,
+ pfx: string,
+ text: list<string>,
+ lnumMap: list<dict<any>>,
+ children: bool)
+ var prefix: string = pfx .. ' '
+ for [symType, symbols] in symbolTypeTable->items()
+ if !children
+ # Add an empty line for the top level symbol types. For types in the
+ # children symbols, don't add the empty line.
+ text->extend([''])
+ lnumMap->extend([{}])
+ endif
+ if children
+ text->extend([prefix .. symType])
+ prefix ..= ' '
+ else
+ text->extend([symType])
+ endif
+ lnumMap->extend([{}])
+ for s in symbols
+ text->add(prefix .. s.name)
+ # remember the line number for the symbol
+ var s_start = s.range.start
+ var start_col: number = util.GetLineByteFromPos(bnr, s_start) + 1
+ lnumMap->add({name: s.name, lnum: s_start.line + 1,
+ col: start_col})
+ s.outlineLine = lnumMap->len()
+ if s->has_key('children') && !s.children->empty()
+ AddSymbolText(bnr, s.children, prefix, text, lnumMap, true)
+ endif
+ endfor
+ endfor
+enddef
+
+# update the symbols displayed in the outline window
+export def UpdateOutlineWindow(fname: string,
+ symbolTypeTable: dict<list<dict<any>>>,
+ symbolLineTable: list<dict<any>>)
+ var wid: number = bufwinid('LSP-Outline')
+ if wid == -1
+ return
+ endif
+
+ # stop refreshing the outline window recursively
+ skipRefresh = true
+
+ var prevWinID: number = win_getid()
+ wid->win_gotoid()
+
+ # if the file displayed in the outline window is same as the new file, then
+ # save and restore the cursor position
+ var symbols = wid->getwinvar('lspSymbols', {})
+ var saveCursor: list<number> = []
+ if !symbols->empty() && symbols.filename == fname
+ saveCursor = getcurpos()
+ endif
+
+ :setlocal modifiable
+ :silent! :%d _
+ setline(1, ['# LSP Outline View',
+ $'# {fname->fnamemodify(":t")} ({fname->fnamemodify(":h")})'])
+
+ # First two lines in the buffer display comment information
+ var lnumMap: list<dict<any>> = [{}, {}]
+ var text: list<string> = []
+ AddSymbolText(fname->bufnr(), symbolTypeTable, '', text, lnumMap, false)
+ text->append('$')
+ w:lspSymbols = {filename: fname, lnumTable: lnumMap,
+ symbolsByLine: symbolLineTable}
+ :setlocal nomodifiable
+
+ if !saveCursor->empty()
+ saveCursor->setpos('.')
+ endif
+
+ prevWinID->win_gotoid()
+
+ # Highlight the current symbol
+ OutlineHighlightCurrentSymbol()
+
+ # re-enable refreshing the outline window
+ skipRefresh = false
+enddef
+
+def OutlineHighlightCurrentSymbol()
+ var fname: string = expand('%')->fnamemodify(':p')
+ if fname->empty() || &filetype->empty()
+ return
+ endif
+
+ var wid: number = bufwinid('LSP-Outline')
+ if wid == -1
+ return
+ endif
+
+ # Check whether the symbols for this file are displayed in the outline
+ # window
+ var lspSymbols = wid->getwinvar('lspSymbols', {})
+ if lspSymbols->empty() || lspSymbols.filename != fname
+ return
+ endif
+
+ var symbolTable: list<dict<any>> = lspSymbols.symbolsByLine
+
+ # line number to locate the symbol
+ var lnum: number = line('.')
+
+ # Find the symbol for the current line number (binary search)
+ var left: number = 0
+ var right: number = symbolTable->len() - 1
+ var mid: number
+ while left <= right
+ mid = (left + right) / 2
+ var r = symbolTable[mid].range
+ if lnum >= (r.start.line + 1) && lnum <= (r.end.line + 1)
+ break
+ endif
+ if lnum > (r.start.line + 1)
+ left = mid + 1
+ else
+ right = mid - 1
+ endif
+ endwhile
+
+ # clear the highlighting in the outline window
+ var bnr: number = wid->winbufnr()
+ prop_remove({bufnr: bnr, type: 'LspOutlineHighlight'})
+
+ if left > right
+ # symbol not found
+ return
+ endif
+
+ # Highlight the selected symbol
+ var col: number =
+ bnr->getbufline(symbolTable[mid].outlineLine)->get(0, '')->match('\S') + 1
+ prop_add(symbolTable[mid].outlineLine, col,
+ {bufnr: bnr, type: 'LspOutlineHighlight',
+ length: symbolTable[mid].name->len()})
+
+ # if the line is not visible, then scroll the outline window to make the
+ # line visible
+ var wininfo = wid->getwininfo()
+ if symbolTable[mid].outlineLine < wininfo[0].topline
+ || symbolTable[mid].outlineLine > wininfo[0].botline
+ var cmd: string = $'call cursor({symbolTable[mid].outlineLine}, 1) | normal z.'
+ win_execute(wid, cmd)
+ endif
+enddef
+
+# when the outline window is closed, do the cleanup
+def OutlineCleanup()
+ # Remove the outline autocommands
+ :silent! autocmd_delete([{group: 'LSPOutline'}])
+
+ :silent! syntax clear LSPTitle
+enddef
+
+# open the symbol outline window
+export def OpenOutlineWindow(cmdmods: string, winsize: number)
+ var wid: number = bufwinid('LSP-Outline')
+ if wid != -1
+ return
+ endif
+
+ var prevWinID: number = win_getid()
+
+ var mods = cmdmods
+ if mods->empty()
+ if opt.lspOptions.outlineOnRight
+ mods = ':vert :botright'
+ else
+ mods = ':vert :topleft'
+ endif
+ endif
+
+ var size = winsize
+ if size == 0
+ size = opt.lspOptions.outlineWinSize
+ endif
+
+ execute $'{mods} :{size}new LSP-Outline'
+ :setlocal modifiable
+ :setlocal noreadonly
+ :silent! :%d _
+ :setlocal buftype=nofile
+ :setlocal bufhidden=delete
+ :setlocal noswapfile nobuflisted
+ :setlocal nonumber norelativenumber fdc=0 nowrap winfixheight winfixwidth
+ :setlocal shiftwidth=2
+ :setlocal foldenable
+ :setlocal foldcolumn=4
+ :setlocal foldlevel=4
+ :setlocal foldmethod=indent
+ setline(1, ['# File Outline'])
+ :nnoremap <silent> <buffer> q :quit<CR>
+ :nnoremap <silent> <buffer> <CR> :call <SID>OutlineJumpToSymbol()<CR>
+ :setlocal nomodifiable
+
+ # highlight all the symbol types
+ :syntax keyword LSPTitle File Module Namespace Package Class Method Property
+ :syntax keyword LSPTitle Field Constructor Enum Interface Function Variable
+ :syntax keyword LSPTitle Constant String Number Boolean Array Object Key Null
+ :syntax keyword LSPTitle EnumMember Struct Event Operator TypeParameter
+
+ if str2nr(&t_Co) > 2
+ :highlight clear LSPTitle
+ :highlight default link LSPTitle Title
+ endif
+
+ prop_type_add('LspOutlineHighlight', {bufnr: bufnr(), highlight: 'Search', override: true})
+
+ try
+ autocmd_delete([{group: 'LSPOutline', event: '*'}])
+ catch /E367:/
+ endtry
+ var acmds: list<dict<any>>
+
+ # Refresh or add the symbols in a buffer to the outline window
+ acmds->add({event: 'BufEnter',
+ group: 'LSPOutline',
+ pattern: '*',
+ cmd: 'call g:LspRequestDocSymbols()'})
+
+ # when the outline window is closed, do the cleanup
+ acmds->add({event: 'BufUnload',
+ group: 'LSPOutline',
+ pattern: 'LSP-Outline',
+ cmd: 'OutlineCleanup()'})
+
+ # Highlight the current symbol when the cursor is not moved for sometime
+ acmds->add({event: 'CursorHold',
+ group: 'LSPOutline',
+ pattern: '*',
+ cmd: 'OutlineHighlightCurrentSymbol()'})
+
+ autocmd_add(acmds)
+
+ prevWinID->win_gotoid()
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim
new file mode 100644
index 0000000..1ef289b
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim
@@ -0,0 +1,107 @@
+vim9script
+
+# Functions related to handling LSP range selection.
+
+import './util.vim'
+
+# Visually (character-wise) select the text in a range
+def SelectText(bnr: number, range: dict<dict<number>>)
+ var rstart = range.start
+ var rend = range.end
+ var start_col: number = util.GetLineByteFromPos(bnr, rstart) + 1
+ var end_col: number = util.GetLineByteFromPos(bnr, rend)
+
+ :normal! v"_y
+ setcharpos("'<", [0, rstart.line + 1, start_col, 0])
+ setcharpos("'>", [0, rend.line + 1, end_col, 0])
+ :normal! gv
+enddef
+
+# Process the range selection reply from LSP server and start a new selection
+export def SelectionStart(lspserver: dict<any>, sel: list<dict<any>>)
+ if sel->empty()
+ return
+ endif
+
+ var bnr: number = bufnr()
+
+ # save the reply for expanding or shrinking the selected text.
+ lspserver.selection = {bnr: bnr, selRange: sel[0], index: 0}
+
+ SelectText(bnr, sel[0].range)
+enddef
+
+# Locate the range in the LSP reply at a specified level
+def GetSelRangeAtLevel(selRange: dict<any>, level: number): dict<any>
+ var r: dict<any> = selRange
+ var idx: number = 0
+
+ while idx != level
+ if !r->has_key('parent')
+ break
+ endif
+ r = r.parent
+ idx += 1
+ endwhile
+
+ return r
+enddef
+
+# Returns true if the current visual selection matches a range in the
+# selection reply from LSP.
+def SelectionFromLSP(range: dict<any>, startpos: list<number>, endpos: list<number>): bool
+ var rstart = range.start
+ var rend = range.end
+ return startpos[1] == rstart.line + 1
+ && endpos[1] == rend.line + 1
+ && startpos[2] == rstart.character + 1
+ && endpos[2] == rend.character
+enddef
+
+# Expand or Shrink the current selection or start a new one.
+export def SelectionModify(lspserver: dict<any>, expand: bool)
+ var fname: string = @%
+ var bnr: number = bufnr()
+
+ if mode() == 'v' && !lspserver.selection->empty()
+ && lspserver.selection.bnr == bnr
+ && !lspserver.selection->empty()
+ # Already in characterwise visual mode and the previous LSP selection
+ # reply for this buffer is available. Modify the current selection.
+
+ var selRange: dict<any> = lspserver.selection.selRange
+ var startpos: list<number> = getcharpos('v')
+ var endpos: list<number> = getcharpos('.')
+ var idx: number = lspserver.selection.index
+
+ # Locate the range in the LSP reply for the current selection
+ selRange = GetSelRangeAtLevel(selRange, lspserver.selection.index)
+
+ # If the current selection is present in the LSP reply, then modify the
+ # selection
+ if SelectionFromLSP(selRange.range, startpos, endpos)
+ if expand
+ # expand the selection
+ if selRange->has_key('parent')
+ selRange = selRange.parent
+ lspserver.selection.index = idx + 1
+ endif
+ else
+ # shrink the selection
+ if idx > 0
+ idx -= 1
+ selRange = GetSelRangeAtLevel(lspserver.selection.selRange, idx)
+ lspserver.selection.index = idx
+ endif
+ endif
+
+ SelectText(bnr, selRange.range)
+ return
+ endif
+ endif
+
+ # Start a new selection
+ lspserver.selectionRange(fname)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim
new file mode 100644
index 0000000..243aea9
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim
@@ -0,0 +1,264 @@
+vim9script
+
+# LSP semantic highlighting functions
+
+import './offset.vim'
+import './options.vim' as opt
+import './buffer.vim' as buf
+
+# Map token type names to higlight group/text property type names
+var TokenTypeMap: dict<string> = {
+ 'namespace': 'LspSemanticNamespace',
+ 'type': 'LspSemanticType',
+ 'class': 'LspSemanticClass',
+ 'enum': 'LspSemanticEnum',
+ 'interface': 'LspSemanticInterface',
+ 'struct': 'LspSemanticStruct',
+ 'typeParameter': 'LspSemanticTypeParameter',
+ 'parameter': 'LspSemanticParameter',
+ 'variable': 'LspSemanticVariable',
+ 'property': 'LspSemanticProperty',
+ 'enumMember': 'LspSemanticEnumMember',
+ 'event': 'LspSemanticEvent',
+ 'function': 'LspSemanticFunction',
+ 'method': 'LspSemanticMethod',
+ 'macro': 'LspSemanticMacro',
+ 'keyword': 'LspSemanticKeyword',
+ 'modifier': 'LspSemanticModifier',
+ 'comment': 'LspSemanticComment',
+ 'string': 'LspSemanticString',
+ 'number': 'LspSemanticNumber',
+ 'regexp': 'LspSemanticRegexp',
+ 'operator': 'LspSemanticOperator',
+ 'decorator': 'LspSemanticDecorator'
+}
+
+export def InitOnce()
+ # Define the default semantic token type highlight groups
+ hlset([
+ {name: 'LspSemanticNamespace', default: true, linksto: 'Type'},
+ {name: 'LspSemanticType', default: true, linksto: 'Type'},
+ {name: 'LspSemanticClass', default: true, linksto: 'Type'},
+ {name: 'LspSemanticEnum', default: true, linksto: 'Type'},
+ {name: 'LspSemanticInterface', default: true, linksto: 'TypeDef'},
+ {name: 'LspSemanticStruct', default: true, linksto: 'Type'},
+ {name: 'LspSemanticTypeParameter', default: true, linksto: 'Type'},
+ {name: 'LspSemanticParameter', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticVariable', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticProperty', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticEnumMember', default: true, linksto: 'Constant'},
+ {name: 'LspSemanticEvent', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticFunction', default: true, linksto: 'Function'},
+ {name: 'LspSemanticMethod', default: true, linksto: 'Function'},
+ {name: 'LspSemanticMacro', default: true, linksto: 'Macro'},
+ {name: 'LspSemanticKeyword', default: true, linksto: 'Keyword'},
+ {name: 'LspSemanticModifier', default: true, linksto: 'Type'},
+ {name: 'LspSemanticComment', default: true, linksto: 'Comment'},
+ {name: 'LspSemanticString', default: true, linksto: 'String'},
+ {name: 'LspSemanticNumber', default: true, linksto: 'Number'},
+ {name: 'LspSemanticRegexp', default: true, linksto: 'String'},
+ {name: 'LspSemanticOperator', default: true, linksto: 'Operator'},
+ {name: 'LspSemanticDecorator', default: true, linksto: 'Macro'}
+ ])
+
+ for hlName in TokenTypeMap->values()
+ prop_type_add(hlName, {highlight: hlName, combine: true})
+ endfor
+enddef
+
+def ParseSemanticTokenMods(lspserverTokenMods: list<string>, tokenMods: number): string
+ var n = tokenMods
+ var tokenMod: number
+ var str = ''
+
+ while n > 0
+ tokenMod = float2nr(log10(and(n, invert(n - 1))) / log10(2))
+ str = $'{str}{lspserverTokenMods[tokenMod]},'
+ n = and(n, n - 1)
+ endwhile
+
+ return str
+enddef
+
+# Apply the edit operations in a semantic tokens delta update message
+# (SemanticTokensDelta) from the language server.
+#
+# The previous list of tokens are stored in the buffer-local
+# LspSemanticTokensData variable. After applying the edits in
+# semTokens.edits, the new set of tokens are returned in semTokens.data.
+def ApplySemanticTokenEdits(bnr: number, semTokens: dict<any>)
+ if semTokens.edits->empty()
+ return
+ endif
+
+ # Need to sort the edits and apply the last edit first.
+ semTokens.edits->sort((a: dict<any>, b: dict<any>) => a.start - b.start)
+
+ # TODO: Remove this code
+ # var d = bnr->getbufvar('LspSemanticTokensData', [])
+ # for e in semTokens.edits
+ # var insertData = e->get('data', [])
+ # d = (e.start > 0 ? d[: e.start - 1] : []) + insertData +
+ # d[e.start + e.deleteCount :]
+ # endfor
+ # semTokens.data = d
+
+ var oldTokens = bnr->getbufvar('LspSemanticTokensData', [])
+ var newTokens = []
+ var idx = 0
+ for e in semTokens.edits
+ if e.start > 0
+ newTokens->extend(oldTokens[idx : e.start - 1])
+ endif
+ newTokens->extend(e->get('data', []))
+ idx = e.start + e.deleteCount
+ endfor
+ newTokens->extend(oldTokens[idx : ])
+ semTokens.data = newTokens
+enddef
+
+# Process a list of semantic tokens and return the corresponding text
+# properties for highlighting.
+def ProcessSemanticTokens(lspserver: dict<any>, bnr: number, tokens: list<number>): dict<list<list<number>>>
+ var props: dict<list<list<number>>> = {}
+ var tokenLine: number = 0
+ var startChar: number = 0
+ var length: number = 0
+ var tokenType: number = 0
+ var tokenMods: number = 0
+ var prevTokenLine = 0
+ var lnum = 1
+ var charIdx = 0
+
+ var lspserverTokenTypes: list<string> =
+ lspserver.semanticTokensLegend.tokenTypes
+ var lspserverTokenMods: list<string> =
+ lspserver.semanticTokensLegend.tokenModifiers
+
+ # Each semantic token uses 5 items in the tokens List
+ var i = 0
+ while i < tokens->len()
+ tokenLine = tokens[i]
+ # tokenLine is relative to the previous token line number
+ lnum += tokenLine
+ if prevTokenLine != lnum
+ # this token is on a different line from the previous token
+ charIdx = 0
+ prevTokenLine = lnum
+ endif
+ startChar = tokens[i + 1]
+ charIdx += startChar
+ length = tokens[i + 2]
+ tokenType = tokens[i + 3]
+ tokenMods = tokens[i + 4]
+
+ var typeStr = lspserverTokenTypes[tokenType]
+ var modStr = ParseSemanticTokenMods(lspserverTokenMods, tokenMods)
+
+ # Decode the semantic token line number, column number and length to
+ # UTF-32 encoding.
+ var r = {
+ start: {
+ line: lnum - 1,
+ character: charIdx
+ },
+ end: {
+ line: lnum - 1,
+ character: charIdx + length
+ }
+ }
+ offset.DecodeRange(lspserver, bnr, r)
+
+ if !props->has_key(typeStr)
+ props[typeStr] = []
+ endif
+ props[typeStr]->add([
+ lnum, r.start.character + 1,
+ lnum, r.end.character + 1
+ ])
+
+ i += 5
+ endwhile
+
+ return props
+enddef
+
+# Parse the semantic highlight reply from the language server and update the
+# text properties
+export def UpdateTokens(lspserver: dict<any>, bnr: number, semTokens: dict<any>)
+
+ if semTokens->has_key('edits')
+ # Delta semantic update. Need to sort the edits and apply the last edit
+ # first.
+ ApplySemanticTokenEdits(bnr, semTokens)
+ endif
+
+ # Cache the semantic tokens in a buffer-local variable, it will be used
+ # later for a delta update.
+ setbufvar(bnr, 'LspSemanticResultId', semTokens->get('resultId', ''))
+ if !semTokens->has_key('data')
+ return
+ endif
+ setbufvar(bnr, 'LspSemanticTokensData', semTokens.data)
+
+ var props: dict<list<list<number>>>
+ props = ProcessSemanticTokens(lspserver, bnr, semTokens.data)
+
+ # First clear all the previous text properties
+ if has('patch-9.0.0233')
+ prop_remove({types: TokenTypeMap->values(), bufnr: bnr, all: true})
+ else
+ for propName in TokenTypeMap->values()
+ prop_remove({type: propName, bufnr: bnr, all: true})
+ endfor
+ endif
+
+ if props->empty()
+ return
+ endif
+
+ # Apply the new text properties
+ for tokenType in TokenTypeMap->keys()
+ if props->has_key(tokenType)
+ prop_add_list({bufnr: bnr, type: TokenTypeMap[tokenType]},
+ props[tokenType])
+ endif
+ endfor
+enddef
+
+# Update the semantic highlighting for buffer "bnr"
+def LspUpdateSemanticHighlight(bnr: number)
+ var lspserver: dict<any> = buf.BufLspServerGet(bnr, 'semanticTokens')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.semanticHighlightUpdate(bnr)
+enddef
+
+# Initialize the semantic highlighting for the buffer 'bnr'
+export def BufferInit(lspserver: dict<any>, bnr: number)
+ if !opt.lspOptions.semanticHighlight || !lspserver.isSemanticTokensProvider
+ # no support for semantic highlighting
+ return
+ endif
+
+ # Highlight all the semantic tokens
+ LspUpdateSemanticHighlight(bnr)
+
+ # buffer-local autocmds for semantic highlighting
+ var acmds: list<dict<any>> = []
+
+ acmds->add({bufnr: bnr,
+ event: 'TextChanged',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspUpdateSemanticHighlight({bnr})'})
+ acmds->add({bufnr: bnr,
+ event: 'BufUnload',
+ group: 'LSPBufferAutocmds',
+ cmd: $"b:LspSemanticTokensData = [] | b:LspSemanticResultId = ''"})
+
+ autocmd_add(acmds)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim
new file mode 100644
index 0000000..ca2bed4
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim
@@ -0,0 +1,145 @@
+vim9script
+
+# Functions related to handling LSP symbol signature help.
+
+import './options.vim' as opt
+import './util.vim'
+import './buffer.vim' as buf
+
+# close the signature popup window
+def CloseSignaturePopup(lspserver: dict<any>)
+ if lspserver.signaturePopup != -1
+ lspserver.signaturePopup->popup_close()
+ endif
+ lspserver.signaturePopup = -1
+enddef
+
+def CloseCurBufSignaturePopup()
+ var lspserver: dict<any> = buf.CurbufGetServer('signatureHelp')
+ if lspserver->empty()
+ return
+ endif
+
+ CloseSignaturePopup(lspserver)
+enddef
+
+# Show the signature using "textDocument/signatureHelp" LSP method
+# Invoked from an insert-mode mapping, so return an empty string.
+def g:LspShowSignature(): string
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('signatureHelp')
+ if lspserver->empty()
+ return ''
+ endif
+
+ # first send all the changes in the current buffer to the LSP server
+ listener_flush()
+ lspserver.showSignature()
+ return ''
+enddef
+
+export def InitOnce()
+ hlset([{name: 'LspSigActiveParameter', default: true, linksto: 'LineNr'}])
+enddef
+
+# Initialize the signature triggers for the current buffer
+export def BufferInit(lspserver: dict<any>)
+ if !lspserver.isSignatureHelpProvider
+ || !lspserver.caps.signatureHelpProvider->has_key('triggerCharacters')
+ # no support for signature help
+ return
+ endif
+
+ if !opt.lspOptions.showSignature
+ || !lspserver.featureEnabled('signatureHelp')
+ # Show signature support is disabled
+ return
+ endif
+
+ # map characters that trigger signature help
+ for ch in lspserver.caps.signatureHelpProvider.triggerCharacters
+ exe $"inoremap <buffer> <silent> {ch} {ch}<C-R>=g:LspShowSignature()<CR>"
+ endfor
+
+ # close the signature popup when leaving insert mode
+ autocmd_add([{bufnr: bufnr(),
+ event: 'InsertLeave',
+ cmd: 'CloseCurBufSignaturePopup()'}])
+enddef
+
+# process the 'textDocument/signatureHelp' reply from the LSP server and
+# display the symbol signature help.
+# Result: SignatureHelp | null
+export def SignatureHelp(lspserver: dict<any>, sighelp: any): void
+ if sighelp->empty()
+ CloseSignaturePopup(lspserver)
+ return
+ endif
+
+ if sighelp.signatures->len() <= 0
+ CloseSignaturePopup(lspserver)
+ return
+ endif
+
+ var sigidx: number = 0
+ if sighelp->has_key('activeSignature')
+ sigidx = sighelp.activeSignature
+ endif
+
+ var sig: dict<any> = sighelp.signatures[sigidx]
+ var text: string = sig.label
+ var hllen: number = 0
+ var startcol: number = 0
+ if sig->has_key('parameters') && sighelp->has_key('activeParameter')
+ var params: list<dict<any>> = sig.parameters
+ var params_len: number = params->len()
+ var activeParam: number = sighelp.activeParameter
+ if params_len > 0 && activeParam < params_len
+ var paramInfo: dict<any> = params[activeParam]
+ var label: any = paramInfo.label
+ if label->type() == v:t_string
+ # label string
+ var label_str: string = label
+ hllen = label_str->len()
+ startcol = text->stridx(label_str)
+ else
+ # [inclusive start offset, exclusive end offset]
+ var label_offset: list<number> = params[activeParam].label
+ var start_offset: number = label_offset[0]
+ var end_offset: number = label_offset[1]
+
+ if has('patch-9.0.1629')
+ # Convert UTF-16 offsets
+ startcol = text->byteidx(start_offset, true)
+ var endcol: number = text->byteidx(end_offset, true)
+ hllen = endcol - startcol
+ else
+ startcol = start_offset
+ hllen = end_offset - start_offset
+ endif
+ endif
+ endif
+ endif
+
+ if opt.lspOptions.echoSignature
+ :echon "\r\r"
+ :echon ''
+ :echon text->strpart(0, startcol)
+ :echoh LspSigActiveParameter
+ :echon text->strpart(startcol, hllen)
+ :echoh None
+ :echon text->strpart(startcol + hllen)
+ else
+ # Close the previous signature popup and open a new one
+ lspserver.signaturePopup->popup_close()
+
+ var popupID = text->popup_atcursor({padding: [0, 1, 0, 1], moved: [col('.') - 1, 9999999]})
+ var bnr: number = popupID->winbufnr()
+ prop_type_add('signature', {bufnr: bnr, highlight: 'LspSigActiveParameter'})
+ if hllen > 0
+ prop_add(1, startcol + 1, {bufnr: bnr, length: hllen, type: 'signature'})
+ endif
+ lspserver.signaturePopup = popupID
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim
new file mode 100644
index 0000000..c03b49d
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim
@@ -0,0 +1,82 @@
+vim9script
+
+# Snippet support
+
+# Integration with the UltiSnips plugin
+export def CompletionUltiSnips(prefix: string, items: list<dict<any>>)
+ call UltiSnips#SnippetsInCurrentScope(1)
+ for key in matchfuzzy(g:current_ulti_dict_info->keys(), prefix)
+ var item = g:current_ulti_dict_info[key]
+ var parts = split(item.location, ':')
+ var txt = parts[0]->readfile()[parts[1]->str2nr() : parts[1]->str2nr() + 20]
+ var restxt = item.description .. "\n\n"
+ for line in txt
+ if line->empty() || line[0 : 6] == "snippet"
+ break
+ else
+ restxt = restxt .. line .. "\n"
+ endif
+ endfor
+ items->add({
+ label: key,
+ data: {
+ entryNames: [key],
+ },
+ kind: 15,
+ documentation: restxt,
+ })
+ endfor
+enddef
+
+# Integration with the vim-vsnip plugin
+export def CompletionVsnip(items: list<dict<any>>)
+ def Pattern(abbr: string): string
+ var chars = escape(abbr, '\/?')->split('\zs')
+ var chars_pattern = '\%(\V' .. chars->join('\m\|\V') .. '\m\)'
+ var separator = chars[0] =~ '\a' ? '\<' : ''
+ return $'{separator}\V{chars[0]}\m{chars_pattern}*$'
+ enddef
+
+ if charcol('.') == 1
+ return
+ endif
+ var starttext = getline('.')->slice(0, charcol('.') - 1)
+ for item in vsnip#get_complete_items(bufnr())
+ var match = starttext->matchstrpos(Pattern(item.abbr))
+ if match[0] != ''
+ var user_data = item.user_data->json_decode()
+ var documentation = []
+ for line in vsnip#to_string(user_data.vsnip.snippet)->split("\n")
+ documentation->add(line)
+ endfor
+ items->add({
+ label: item.abbr,
+ filterText: item.word,
+ insertTextFormat: 2,
+ textEdit: {
+ newText: user_data.vsnip.snippet->join("\n"),
+ range: {
+ start: {
+ line: line('.'),
+ character: match[1],
+ },
+ ['end']: {
+ line: line('.'),
+ character: match[2],
+ },
+ },
+ },
+ data: {
+ entryNames: [item.word],
+ },
+ kind: 15,
+ documentation: {
+ kind: 'markdown',
+ value: documentation->join("\n"),
+ },
+ })
+ endif
+ endfor
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim
new file mode 100644
index 0000000..bcd547a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim
@@ -0,0 +1,1014 @@
+vim9script
+
+# Functions for dealing with symbols.
+# - LSP symbol menu and for searching symbols across the workspace.
+# - show locations
+# - jump to a symbol definition, declaration, type definition or
+# implementation
+
+import './options.vim' as opt
+import './util.vim'
+import './outline.vim'
+
+# Initialize the highlight group and the text property type used for
+# document symbol search
+export def InitOnce()
+ # Use a high priority value to override other highlights in the line
+ hlset([
+ {name: 'LspSymbolName', default: true, linksto: 'Search'},
+ {name: 'LspSymbolRange', default: true, linksto: 'Visual'}
+ ])
+ prop_type_add('LspSymbolNameProp', {highlight: 'LspSymbolName',
+ combine: false,
+ override: true,
+ priority: 201})
+ prop_type_add('LspSymbolRangeProp', {highlight: 'LspSymbolRange',
+ combine: false,
+ override: true,
+ priority: 200})
+enddef
+
+# Handle keys pressed when the workspace symbol popup menu is displayed
+def FilterSymbols(lspserver: dict<any>, popupID: number, key: string): bool
+ var key_handled: bool = false
+ var update_popup: bool = false
+ var query: string = lspserver.workspaceSymbolQuery
+
+ if key == "\<BS>" || key == "\<C-H>"
+ # Erase one character from the filter text
+ if query->len() >= 1
+ query = query[: -2]
+ update_popup = true
+ endif
+ key_handled = true
+ elseif key == "\<C-U>"
+ # clear the filter text
+ query = ''
+ update_popup = true
+ key_handled = true
+ elseif key == "\<C-F>"
+ || key == "\<C-B>"
+ || key == "\<PageUp>"
+ || key == "\<PageDown>"
+ || key == "\<C-Home>"
+ || key == "\<C-End>"
+ || key == "\<C-N>"
+ || key == "\<C-P>"
+ # scroll the popup window
+ var cmd: string = 'normal! ' .. (key == "\<C-N>" ? 'j' : key == "\<C-P>" ? 'k' : key)
+ win_execute(popupID, cmd)
+ key_handled = true
+ elseif key == "\<Up>" || key == "\<Down>"
+ # Use native Vim handling for these keys
+ key_handled = false
+ elseif key =~ '^\f$' || key == "\<Space>"
+ # Filter the names based on the typed key and keys typed before
+ query ..= key
+ update_popup = true
+ key_handled = true
+ endif
+
+ if update_popup
+ # Update the popup with the new list of symbol names
+ popupID->popup_settext('')
+ if query != ''
+ lspserver.workspaceQuery(query, false)
+ else
+ []->setwinvar(popupID, 'LspSymbolTable')
+ endif
+ :echo $'Symbol: {query}'
+ endif
+
+ # Update the workspace symbol query string
+ lspserver.workspaceSymbolQuery = query
+
+ if key_handled
+ return true
+ endif
+
+ return popupID->popup_filter_menu(key)
+enddef
+
+# Jump to the location of a symbol selected in the popup menu
+def JumpToWorkspaceSymbol(cmdmods: string, popupID: number, result: number): void
+ # clear the message displayed at the command-line
+ :echo ''
+
+ if result <= 0
+ # popup is canceled
+ return
+ endif
+
+ var symTbl: list<dict<any>> = popupID->getwinvar('LspSymbolTable', [])
+ if symTbl->empty()
+ return
+ endif
+ try
+ # Save the current location in the tag stack
+ util.PushCursorToTagStack()
+
+ # if the selected file is already present in a window, then jump to it
+ var fname: string = symTbl[result - 1].file
+ var bnr = fname->bufnr()
+ if cmdmods->empty()
+ var winList: list<number> = bnr->win_findbuf()
+ if winList->empty()
+ # Not present in any window
+ if &modified || &buftype != ''
+ # the current buffer is modified or is not a normal buffer, then
+ # open the file in a new window
+ exe $'split {symTbl[result - 1].file}'
+ else
+ exe $'confirm edit {symTbl[result - 1].file}'
+ endif
+ else
+ # If the target buffer is opened in the current window, then don't
+ # change the window.
+ if bufnr() != bnr
+ # If the target buffer is opened in a window in the current tab
+ # page, then use it.
+ var winID = fname->bufwinid()
+ if winID == -1
+ # not present in the current tab page. Use the first window.
+ winID = winList[0]
+ endif
+ winID->win_gotoid()
+ endif
+ endif
+ else
+ exe $'{cmdmods} split {symTbl[result - 1].file}'
+ endif
+ # Set the previous cursor location mark. Instead of using setpos(), m' is
+ # used so that the current location is added to the jump list.
+ :normal m'
+ setcursorcharpos(symTbl[result - 1].pos.line + 1,
+ util.GetCharIdxWithoutCompChar(bufnr(),
+ symTbl[result - 1].pos) + 1)
+ :normal! zv
+ catch
+ # ignore exceptions
+ endtry
+enddef
+
+# display a list of symbols from the workspace
+def ShowSymbolMenu(lspserver: dict<any>, query: string, cmdmods: string)
+ # Create the popup menu
+ var lnum = &lines - &cmdheight - 2 - 10
+ var popupAttr = {
+ title: 'Workspace Symbol Search',
+ wrap: false,
+ pos: 'topleft',
+ line: lnum,
+ col: 2,
+ minwidth: 60,
+ minheight: 10,
+ maxheight: 10,
+ maxwidth: 60,
+ mapping: false,
+ fixed: 1,
+ close: 'button',
+ filter: function(FilterSymbols, [lspserver]),
+ callback: function('JumpToWorkspaceSymbol', [cmdmods])
+ }
+ lspserver.workspaceSymbolPopup = popup_menu([], popupAttr)
+ lspserver.workspaceSymbolQuery = query
+ prop_type_add('lspworkspacesymbol',
+ {bufnr: lspserver.workspaceSymbolPopup->winbufnr(),
+ highlight: 'Title'})
+ :echo $'Symbol: {query}'
+enddef
+
+# Convert a file name to <filename> (<dirname>) format.
+# Make sure the popup doesn't occupy the entire screen by reducing the width.
+def MakeMenuName(popupWidth: number, fname: string): string
+ var filename: string = fname->fnamemodify(':t')
+ var flen: number = filename->len()
+ var dirname: string = fname->fnamemodify(':h')
+
+ if fname->len() > popupWidth && flen < popupWidth
+ # keep the full file name and reduce directory name length
+ # keep some characters at the beginning and end (equally).
+ # 6 spaces are used for "..." and " ()"
+ var dirsz = (popupWidth - flen - 6) / 2
+ dirname = dirname[: dirsz] .. '...' .. dirname[-dirsz : ]
+ endif
+ var str: string = filename
+ if dirname != '.'
+ str ..= $' ({dirname}/)'
+ endif
+ return str
+enddef
+
+# process the 'workspace/symbol' reply from the LSP server
+# Result: SymbolInformation[] | null
+export def WorkspaceSymbolPopup(lspserver: dict<any>, query: string,
+ symInfo: list<dict<any>>, cmdmods: string)
+ var symbols: list<dict<any>> = []
+ var symbolType: string
+ var fileName: string
+ var symName: string
+
+ # Create a symbol popup menu if it is not present
+ if lspserver.workspaceSymbolPopup->winbufnr() == -1
+ ShowSymbolMenu(lspserver, query, cmdmods)
+ endif
+
+ for symbol in symInfo
+ if !symbol->has_key('location')
+ # ignore entries without location information
+ continue
+ endif
+
+ # interface SymbolInformation
+ fileName = util.LspUriToFile(symbol.location.uri)
+
+ symName = symbol.name
+ if symbol->has_key('containerName') && symbol.containerName != ''
+ symName = $'{symbol.containerName}::{symName}'
+ endif
+ symName ..= $' [{SymbolKindToName(symbol.kind)}]'
+ symName ..= ' ' .. MakeMenuName(
+ lspserver.workspaceSymbolPopup->popup_getpos().core_width,
+ fileName)
+
+ symbols->add({name: symName,
+ file: fileName,
+ pos: symbol.location.range.start})
+ endfor
+ symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable')
+ lspserver.workspaceSymbolPopup->popup_settext(
+ symbols->copy()->mapnew('v:val.name'))
+enddef
+
+# map the LSP symbol kind number to string
+export def SymbolKindToName(symkind: number): string
+ var symbolMap: list<string> = [
+ '',
+ 'File',
+ 'Module',
+ 'Namespace',
+ 'Package',
+ 'Class',
+ 'Method',
+ 'Property',
+ 'Field',
+ 'Constructor',
+ 'Enum',
+ 'Interface',
+ 'Function',
+ 'Variable',
+ 'Constant',
+ 'String',
+ 'Number',
+ 'Boolean',
+ 'Array',
+ 'Object',
+ 'Key',
+ 'Null',
+ 'EnumMember',
+ 'Struct',
+ 'Event',
+ 'Operator',
+ 'TypeParameter'
+ ]
+ if symkind > 26
+ return ''
+ endif
+ return symbolMap[symkind]
+enddef
+
+def UpdatePeekFilePopup(lspserver: dict<any>, locations: list<dict<any>>)
+ if lspserver.peekSymbolPopup->winbufnr() == -1
+ return
+ endif
+
+ lspserver.peekSymbolFilePopup->popup_close()
+
+ var n = line('.', lspserver.peekSymbolPopup) - 1
+ var [uri, range] = util.LspLocationParse(locations[n])
+ var fname: string = util.LspUriToFile(uri)
+
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ bnr = fname->bufadd()
+ endif
+
+ var popupAttrs = {
+ title: $"{fname->fnamemodify(':t')} ({fname->fnamemodify(':h')})",
+ wrap: false,
+ fixed: true,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: winwidth(0) - 38,
+ maxwidth: winwidth(0) - 38,
+ cursorline: true,
+ border: [],
+ mapping: false,
+ line: 'cursor+1',
+ col: 1
+ }
+
+ lspserver.peekSymbolFilePopup = popup_create(bnr, popupAttrs)
+ var rstart = range.start
+ var cmds =<< trim eval END
+ :setlocal number
+ [{rstart.line + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(lspserver.peekSymbolFilePopup, cmds)
+
+ lspserver.peekSymbolFilePopup->clearmatches()
+ var start_col = util.GetLineByteFromPos(bnr, rstart) + 1
+ var end_col = util.GetLineByteFromPos(bnr, range.end)
+ var pos = [[rstart.line + 1,
+ start_col, end_col - start_col + 1]]
+ matchaddpos('Search', pos, 10, -1, {window: lspserver.peekSymbolFilePopup})
+enddef
+
+def LocPopupFilter(lspserver: dict<any>, locations: list<dict<any>>,
+ popup_id: number, key: string): bool
+ popup_filter_menu(popup_id, key)
+ if lspserver.peekSymbolPopup->winbufnr() == -1
+ if lspserver.peekSymbolFilePopup->winbufnr() != -1
+ lspserver.peekSymbolFilePopup->popup_close()
+ endif
+ lspserver.peekSymbolPopup = -1
+ lspserver.peekSymbolFilePopup = -1
+ else
+ UpdatePeekFilePopup(lspserver, locations)
+ endif
+ return true
+enddef
+
+def LocPopupCallback(lspserver: dict<any>, locations: list<dict<any>>,
+ popup_id: number, selIdx: number)
+ if lspserver.peekSymbolFilePopup->winbufnr() != -1
+ lspserver.peekSymbolFilePopup->popup_close()
+ endif
+ lspserver.peekSymbolPopup = -1
+ if selIdx != -1
+ util.PushCursorToTagStack()
+ util.JumpToLspLocation(locations[selIdx - 1], '')
+ endif
+enddef
+
+# Display the locations in a popup menu. Display the corresponding file in
+# an another popup window.
+def PeekLocations(lspserver: dict<any>, locations: list<dict<any>>,
+ title: string)
+ if lspserver.peekSymbolPopup->winbufnr() != -1
+ # If the symbol popup window is already present, close it.
+ lspserver.peekSymbolPopup->popup_close()
+ endif
+
+ var w: number = &columns
+ var fnamelen = float2nr(w * 0.4)
+
+ var curlnum = line('.')
+ var symIdx = 1
+ var curSymIdx = 1
+ var menuItems: list<string> = []
+ for loc in locations
+ var [uri, range] = util.LspLocationParse(loc)
+ var fname: string = util.LspUriToFile(uri)
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ bnr = fname->bufadd()
+ endif
+ :silent! bnr->bufload()
+
+ var lnum = range.start.line + 1
+ var text: string = bnr->getbufline(lnum)->get(0, '')
+ menuItems->add($'{lnum}: {text}')
+
+ if lnum == curlnum
+ curSymIdx = symIdx
+ endif
+ symIdx += 1
+ endfor
+
+ var popupAttrs = {
+ title: title,
+ wrap: false,
+ pos: 'topleft',
+ line: 'cursor+1',
+ col: winwidth(0) - 34,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: 30,
+ maxwidth: 30,
+ mapping: false,
+ fixed: true,
+ filter: function(LocPopupFilter, [lspserver, locations]),
+ callback: function(LocPopupCallback, [lspserver, locations])
+ }
+ lspserver.peekSymbolPopup = popup_menu(menuItems, popupAttrs)
+ # Select the current symbol in the menu
+ var cmds =<< trim eval END
+ [{curSymIdx}, 1]->cursor()
+ END
+ win_execute(lspserver.peekSymbolPopup, cmds, 'silent!')
+ UpdatePeekFilePopup(lspserver, locations)
+enddef
+
+export def ShowLocations(lspserver: dict<any>, locations: list<dict<any>>,
+ peekSymbol: bool, title: string)
+ if peekSymbol
+ PeekLocations(lspserver, locations, title)
+ return
+ endif
+
+ # create a loclist the location of the locations
+ var qflist: list<dict<any>> = []
+ for loc in locations
+ var [uri, range] = util.LspLocationParse(loc)
+ var fname: string = util.LspUriToFile(uri)
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ bnr = fname->bufadd()
+ endif
+ :silent! bnr->bufload()
+ var rstart = range.start
+ var text: string = bnr->getbufline(rstart.line + 1)->get(0, '')->trim("\t ", 1)
+ qflist->add({filename: fname,
+ lnum: rstart.line + 1,
+ col: util.GetLineByteFromPos(bnr, rstart) + 1,
+ text: text})
+ endfor
+
+ var save_winid = win_getid()
+
+ if opt.lspOptions.useQuickfixForLocations
+ setqflist([], ' ', {title: title, items: qflist})
+ var mods: string = ''
+ exe $'{mods} copen'
+ else
+ setloclist(0, [], ' ', {title: title, items: qflist})
+ var mods: string = ''
+ exe $'{mods} lopen'
+ endif
+
+ if !opt.lspOptions.keepFocusInReferences
+ save_winid->win_gotoid()
+ endif
+enddef
+
+# Key filter callback function used for the symbol popup window.
+# Vim doesn't close the popup window when the escape key is pressed.
+# This is function supports that.
+def SymbolFilterCB(lspserver: dict<any>, id: number, key: string): bool
+ if key == "\<Esc>"
+ lspserver.peekSymbolPopup->popup_close()
+ return true
+ endif
+
+ return false
+enddef
+
+# Display the file specified by LSP "LocationLink" in a popup window and
+# highlight the range in "location".
+def PeekSymbolLocation(lspserver: dict<any>, location: dict<any>)
+ var [uri, range] = util.LspLocationParse(location)
+ var fname = util.LspUriToFile(uri)
+ var bnum = fname->bufadd()
+ if bnum == 0
+ # Failed to create or find a buffer
+ return
+ endif
+ :silent! bnum->bufload()
+
+ if lspserver.peekSymbolPopup->winbufnr() != -1
+ # If the symbol popup window is already present, close it.
+ lspserver.peekSymbolPopup->popup_close()
+ endif
+ var CbFunc = function(SymbolFilterCB, [lspserver])
+ var popupAttrs = {
+ title: $"{fnamemodify(fname, ':t')} ({fnamemodify(fname, ':h')})",
+ wrap: false,
+ moved: 'any',
+ minheight: 10,
+ maxheight: 10,
+ minwidth: 10,
+ maxwidth: 60,
+ cursorline: true,
+ border: [],
+ mapping: false,
+ filter: CbFunc
+ }
+ lspserver.peekSymbolPopup = popup_atcursor(bnum, popupAttrs)
+
+ # Highlight the symbol name and center the line in the popup
+ var pwid = lspserver.peekSymbolPopup
+ var pwbuf = pwid->winbufnr()
+ var pos: list<number> = []
+ var start_col: number
+ var end_col: number
+ var rstart = range.start
+ start_col = util.GetLineByteFromPos(pwbuf, rstart) + 1
+ end_col = util.GetLineByteFromPos(pwbuf, range.end) + 1
+ pos->add(rstart.line + 1)
+ pos->extend([start_col, end_col - start_col])
+ matchaddpos('Search', [pos], 10, 101, {window: pwid})
+ var cmds =<< trim eval END
+ :setlocal number
+ [{rstart.line + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(pwid, cmds, 'silent!')
+enddef
+
+# Jump to the definition, declaration or implementation of a symbol.
+# Also, used to peek at the definition, declaration or implementation of a
+# symbol.
+export def GotoSymbol(lspserver: dict<any>, location: dict<any>,
+ peekSymbol: bool, cmdmods: string)
+ if peekSymbol
+ PeekSymbolLocation(lspserver, location)
+ else
+ # Save the current cursor location in the tag stack.
+ util.PushCursorToTagStack()
+ util.JumpToLspLocation(location, cmdmods)
+ endif
+enddef
+
+# Process the LSP server reply message for a 'textDocument/definition' request
+# and return a list of Dicts in a format accepted by the 'tagfunc' option.
+export def TagFunc(lspserver: dict<any>,
+ taglocations: list<dict<any>>,
+ pat: string): list<dict<any>>
+ var retval: list<dict<any>>
+
+ for tagloc in taglocations
+ var tagitem = {}
+ tagitem.name = pat
+
+ var [uri, range] = util.LspLocationParse(tagloc)
+ tagitem.filename = util.LspUriToFile(uri)
+ var bnr = util.LspUriToBufnr(uri)
+ var rstart = range.start
+ var startByteIdx = util.GetLineByteFromPos(bnr, rstart)
+ tagitem.cmd = $"/\\%{rstart.line + 1}l\\%{startByteIdx + 1}c"
+
+ retval->add(tagitem)
+ endfor
+
+ return retval
+enddef
+
+# process SymbolInformation[]
+def ProcessSymbolInfoTable(lspserver: dict<any>,
+ bnr: number,
+ symbolInfoTable: list<dict<any>>,
+ symbolTypeTable: dict<list<dict<any>>>,
+ symbolLineTable: list<dict<any>>)
+ var fname: string
+ var symbolType: string
+ var name: string
+ var r: dict<dict<number>>
+ var symInfo: dict<any>
+
+ for syminfo in symbolInfoTable
+ fname = util.LspUriToFile(syminfo.location.uri)
+ symbolType = SymbolKindToName(syminfo.kind)
+ name = syminfo.name
+ if syminfo->has_key('containerName')
+ if syminfo.containerName != ''
+ name ..= $' [{syminfo.containerName}]'
+ endif
+ endif
+ r = syminfo.location.range
+ lspserver.decodeRange(bnr, r)
+
+ if !symbolTypeTable->has_key(symbolType)
+ symbolTypeTable[symbolType] = []
+ endif
+ symInfo = {name: name, range: r}
+ symbolTypeTable[symbolType]->add(symInfo)
+ symbolLineTable->add(symInfo)
+ endfor
+enddef
+
+# process DocumentSymbol[]
+def ProcessDocSymbolTable(lspserver: dict<any>,
+ bnr: number,
+ docSymbolTable: list<dict<any>>,
+ symbolTypeTable: dict<list<dict<any>>>,
+ symbolLineTable: list<dict<any>>)
+ var symbolType: string
+ var name: string
+ var r: dict<dict<number>>
+ var symInfo: dict<any>
+ var symbolDetail: string
+ var childSymbols: dict<list<dict<any>>>
+
+ for syminfo in docSymbolTable
+ name = syminfo.name
+ symbolType = SymbolKindToName(syminfo.kind)
+ r = syminfo.selectionRange
+ lspserver.decodeRange(bnr, r)
+ if syminfo->has_key('detail')
+ symbolDetail = syminfo.detail
+ endif
+ if !symbolTypeTable->has_key(symbolType)
+ symbolTypeTable[symbolType] = []
+ endif
+ childSymbols = {}
+ if syminfo->has_key('children')
+ ProcessDocSymbolTable(lspserver, bnr, syminfo.children, childSymbols,
+ symbolLineTable)
+ endif
+ symInfo = {name: name, range: r, detail: symbolDetail,
+ children: childSymbols}
+ symbolTypeTable[symbolType]->add(symInfo)
+ symbolLineTable->add(symInfo)
+ endfor
+enddef
+
+# process the 'textDocument/documentSymbol' reply from the LSP server
+# Open a symbols window and display the symbols as a tree
+# Result: DocumentSymbol[] | SymbolInformation[] | null
+export def DocSymbolOutline(lspserver: dict<any>, docSymbol: any, fname: string)
+ var bnr = fname->bufnr()
+ var symbolTypeTable: dict<list<dict<any>>> = {}
+ var symbolLineTable: list<dict<any>> = []
+
+ if docSymbol->empty()
+ # No symbols defined for this file. Clear the outline window.
+ outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
+ return
+ endif
+
+ if docSymbol[0]->has_key('location')
+ # SymbolInformation[]
+ ProcessSymbolInfoTable(lspserver, bnr, docSymbol, symbolTypeTable,
+ symbolLineTable)
+ else
+ # DocumentSymbol[]
+ ProcessDocSymbolTable(lspserver, bnr, docSymbol, symbolTypeTable,
+ symbolLineTable)
+ endif
+
+ # sort the symbols by line number
+ symbolLineTable->sort((a, b) => a.range.start.line - b.range.start.line)
+ outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
+enddef
+
+# Process the list of symbols (LSP interface "SymbolInformation") in
+# "symbolInfoTable". For each symbol, create the name to display in the popup
+# menu along with the symbol range and return the List.
+def GetSymbolsInfoTable(lspserver: dict<any>,
+ bnr: number,
+ symbolInfoTable: list<dict<any>>): list<dict<any>>
+ var symbolTable: list<dict<any>> = []
+ var symbolType: string
+ var name: string
+ var containerName: string
+ var r: dict<dict<number>>
+
+ for syminfo in symbolInfoTable
+ symbolType = SymbolKindToName(syminfo.kind)
+ name = $'{symbolType} : {syminfo.name}'
+ if syminfo->has_key('containerName') && !syminfo.containerName->empty()
+ name ..= $' [{syminfo.containerName}]'
+ endif
+ r = syminfo.location.range
+ lspserver.decodeRange(bnr, r)
+
+ symbolTable->add({name: name, range: r, selectionRange: {}})
+ endfor
+
+ return symbolTable
+enddef
+
+# Process the list of symbols (LSP interface "DocumentSymbol") in
+# "docSymbolTable". For each symbol, create the name to display in the popup
+# menu along with the symbol range and return the List in "symbolTable"
+def GetSymbolsDocSymbol(lspserver: dict<any>,
+ bnr: number,
+ docSymbolTable: list<dict<any>>,
+ symbolTable: list<dict<any>>,
+ parentName: string = '')
+ var symbolType: string
+ var name: string
+ var r: dict<dict<number>>
+ var sr: dict<dict<number>>
+ var symInfo: dict<any>
+
+ for syminfo in docSymbolTable
+ var symName = syminfo.name
+ symbolType = SymbolKindToName(syminfo.kind)->tolower()
+ sr = syminfo.selectionRange
+ lspserver.decodeRange(bnr, sr)
+ r = syminfo.range
+ lspserver.decodeRange(bnr, r)
+ name = $'{symbolType} : {symName}'
+ if parentName != ''
+ name ..= $' [{parentName}]'
+ endif
+ # TODO: Should include syminfo.detail? Will it clutter the menu?
+ symInfo = {name: name, range: r, selectionRange: sr}
+ symbolTable->add(symInfo)
+
+ if syminfo->has_key('children')
+ # Process all the child symbols
+ GetSymbolsDocSymbol(lspserver, bnr, syminfo.children, symbolTable,
+ symName)
+ endif
+ endfor
+enddef
+
+# Highlight the name and the range of lines for the symbol at symTbl[symIdx]
+def SymbolHighlight(symTbl: list<dict<any>>, symIdx: number)
+ prop_remove({type: 'LspSymbolNameProp', all: true})
+ prop_remove({type: 'LspSymbolRangeProp', all: true})
+ if symTbl->empty()
+ return
+ endif
+
+ var r = symTbl[symIdx].range
+ if r->empty()
+ return
+ endif
+ var rangeStart = r.start
+ var rangeEnd = r.end
+ var start_lnum = rangeStart.line + 1
+ var start_col = rangeStart.character + 1
+ var end_lnum = rangeEnd.line + 1
+ var end_col: number
+ var last_lnum = line('$')
+ if end_lnum > line('$')
+ end_lnum = last_lnum
+ end_col = col([last_lnum, '$'])
+ else
+ end_col = rangeEnd.character + 1
+ endif
+ prop_add(start_lnum, start_col,
+ {type: 'LspSymbolRangeProp',
+ end_lnum: end_lnum,
+ end_col: end_col})
+ cursor(start_lnum, 1)
+ :normal! z.
+
+ var sr = symTbl[symIdx].selectionRange
+ if sr->empty()
+ return
+ endif
+ rangeStart = sr.start
+ rangeEnd = sr.end
+ prop_add(rangeStart.line + 1, 1,
+ {type: 'LspSymbolNameProp',
+ start_col: rangeStart.character + 1,
+ end_lnum: rangeEnd.line + 1,
+ end_col: rangeEnd.character + 1})
+enddef
+
+# Callback invoked when an item is selected in the symbol popup menu
+# "symTbl" - list of symbols
+# "symInputPopup" - Symbol search input popup window ID
+# "save_curpos" - Cursor position before invoking the symbol search. If the
+# symbol search is canceled, restore the cursor to this
+# position.
+def SymbolMenuItemSelected(symPopupMenu: number,
+ result: number)
+ var symTblFiltered = symPopupMenu->getwinvar('symbolTableFiltered', [])
+ var symInputPopup = symPopupMenu->getwinvar('symbolInputPopup', 0)
+ var save_curpos = symPopupMenu->getwinvar('saveCurPos', [])
+
+ # Restore the cursor to the location where the command was invoked
+ setpos('.', save_curpos)
+
+ if result > 0
+ # A symbol is selected in the popup menu
+
+ # Set the previous cursor location mark. Instead of using setpos(), m' is
+ # used so that the current location is added to the jump list.
+ :normal m'
+
+ # Jump to the selected symbol location
+ var r = symTblFiltered[result - 1].selectionRange
+ if r->empty()
+ # SymbolInformation doesn't have the selectionRange field
+ r = symTblFiltered[result - 1].range
+ endif
+ setcursorcharpos(r.start.line + 1,
+ util.GetCharIdxWithoutCompChar(bufnr(), r.start) + 1)
+ :normal! zv
+ endif
+ symInputPopup->popup_close()
+ prop_remove({type: 'LspSymbolNameProp', all: true})
+ prop_remove({type: 'LspSymbolRangeProp', all: true})
+enddef
+
+# Key filter function for the symbol popup menu.
+def SymbolMenuFilterKey(symPopupMenu: number,
+ key: string): bool
+ var keyHandled = true
+ var updateInputPopup = false
+ var inputText = symPopupMenu->getwinvar('inputText', '')
+ var symInputPopup = symPopupMenu->getwinvar('symbolInputPopup', 0)
+
+ if key == "\<BS>" || key == "\<C-H>"
+ # Erase a character in the input popup
+ if inputText->len() >= 1
+ inputText = inputText[: -2]
+ updateInputPopup = true
+ else
+ keyHandled = false
+ endif
+ elseif key == "\<C-U>"
+ # Erase all the characters in the input popup
+ inputText = ''
+ updateInputPopup = true
+ elseif key == "\<tab>"
+ || key == "\<C-n>"
+ || key == "\<Down>"
+ || key == "\<ScrollWheelDown>"
+ var ln = getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! j")
+ if ln == getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! gg")
+ endif
+ elseif key == "\<S-tab>"
+ || key == "\<C-p>"
+ || key == "\<Up>"
+ || key == "\<ScrollWheelUp>"
+ var ln = getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! k")
+ if ln == getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! G")
+ endif
+ elseif key == "\<PageDown>"
+ win_execute(symPopupMenu, "normal! \<C-d>")
+ elseif key == "\<PageUp>"
+ win_execute(symPopupMenu, "normal! \<C-u>")
+ elseif key == "\<C-F>"
+ || key == "\<C-B>"
+ || key == "\<C-Home>"
+ || key == "\<C-End>"
+ win_execute(symPopupMenu, $"normal! {key}")
+ elseif key =~ '^\k$'
+ # A keyword character is typed. Add to the input text and update the
+ # popup
+ inputText ..= key
+ updateInputPopup = true
+ else
+ keyHandled = false
+ endif
+
+ var symTblFiltered: list<dict<any>> = []
+ symTblFiltered = symPopupMenu->getwinvar('symbolTableFiltered', [])
+
+ if updateInputPopup
+ # Update the input popup with the new text and update the symbol popup
+ # window with the matching symbol names.
+ symInputPopup->popup_settext(inputText)
+
+ var symbolTable = symPopupMenu->getwinvar('symbolTable')
+ symTblFiltered = symbolTable->deepcopy()
+ var symbolMatchPos: list<list<number>> = []
+
+ # Get the list of symbols fuzzy matching the entered text
+ if inputText != ''
+ var t = symTblFiltered->matchfuzzypos(inputText, {key: 'name'})
+ symTblFiltered = t[0]
+ symbolMatchPos = t[1]
+ endif
+
+ var popupText: list<dict<any>>
+ var text: list<dict<any>>
+ if !symbolMatchPos->empty()
+ # Generate a list of symbol names and the corresponding text properties
+ # to highlight the matching characters.
+ popupText = symTblFiltered->mapnew((idx, val): dict<any> => ({
+ text: val.name,
+ props: symbolMatchPos[idx]->mapnew((_, w: number): dict<any> => ({
+ col: w + 1,
+ length: 1,
+ type: 'LspSymbolMatch'}
+ ))}
+ ))
+ else
+ popupText = symTblFiltered->mapnew((idx, val): dict<string> => {
+ return {text: val.name}
+ })
+ endif
+ symPopupMenu->popup_settext(popupText)
+
+ # Select the first symbol and highlight the corresponding text range
+ win_execute(symPopupMenu, 'cursor(1, 1)')
+ SymbolHighlight(symTblFiltered, 0)
+ endif
+
+ # Save the filtered symbol table and the search text in popup window
+ # variables
+ setwinvar(symPopupMenu, 'inputText', inputText)
+ setwinvar(symPopupMenu, 'symbolTableFiltered', symTblFiltered)
+
+ if !keyHandled
+ # Use the default handler for the key
+ symPopupMenu->popup_filter_menu(key)
+ endif
+
+ # Highlight the name and range of the selected symbol
+ var lnum = line('.', symPopupMenu) - 1
+ if lnum >= 0
+ SymbolHighlight(symTblFiltered, lnum)
+ endif
+
+ return true
+enddef
+
+# Display the symbols popup menu
+def SymbolPopupMenu(symbolTable: list<dict<any>>)
+ var curLine = line('.')
+ var curSymIdx = 0
+
+ # Get the names of all the symbols. Also get the index of the symbol under
+ # the cursor.
+ var symNames = symbolTable->mapnew((idx, val): string => {
+ var r = val.range
+ if !r->empty() && curSymIdx == 0
+ if curLine >= r.start.line + 1 && curLine <= r.end.line + 1
+ curSymIdx = idx
+ endif
+ endif
+ return val.name
+ })
+
+ var symInputPopupAttr = {
+ title: 'Select Symbol',
+ wrap: false,
+ pos: 'topleft',
+ line: &lines - 14,
+ col: 10,
+ minwidth: 60,
+ minheight: 1,
+ maxheight: 1,
+ maxwidth: 60,
+ fixed: 1,
+ close: 'button',
+ border: []
+ }
+ var symInputPopup = popup_create('', symInputPopupAttr)
+
+ var symNamesPopupattr = {
+ wrap: false,
+ pos: 'topleft',
+ line: &lines - 11,
+ col: 10,
+ minwidth: 60,
+ minheight: 10,
+ maxheight: 10,
+ maxwidth: 60,
+ fixed: 1,
+ border: [0, 0, 0, 0],
+ callback: SymbolMenuItemSelected,
+ filter: SymbolMenuFilterKey,
+ }
+ var symPopupMenu = popup_menu(symNames, symNamesPopupattr)
+
+ # Save the state in the popup menu window variables
+ setwinvar(symPopupMenu, 'symbolTable', symbolTable)
+ setwinvar(symPopupMenu, 'symbolTableFiltered', symbolTable->deepcopy())
+ setwinvar(symPopupMenu, 'symbolInputPopup', symInputPopup)
+ setwinvar(symPopupMenu, 'saveCurPos', getcurpos())
+ prop_type_add('LspSymbolMatch', {bufnr: symPopupMenu->winbufnr(),
+ highlight: 'Title',
+ override: true})
+
+ # Start with the symbol under the cursor
+ var cmds =<< trim eval END
+ [{curSymIdx + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(symPopupMenu, cmds, 'silent!')
+
+ # Highlight the name and range of the first symbol
+ SymbolHighlight(symbolTable, curSymIdx)
+enddef
+
+# process the 'textDocument/documentSymbol' reply from the LSP server
+# Result: DocumentSymbol[] | SymbolInformation[] | null
+# Display the symbols in a popup window and jump to the selected symbol
+export def DocSymbolPopup(lspserver: dict<any>, docSymbol: any, fname: string)
+ var symList: list<dict<any>> = []
+
+ if docSymbol->empty()
+ return
+ endif
+
+ var bnr = fname->bufnr()
+
+ if docSymbol[0]->has_key('location')
+ # SymbolInformation[]
+ symList = GetSymbolsInfoTable(lspserver, bnr, docSymbol)
+ else
+ # DocumentSymbol[]
+ GetSymbolsDocSymbol(lspserver, bnr, docSymbol, symList)
+ endif
+
+ :redraw!
+ SymbolPopupMenu(symList)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim
new file mode 100644
index 0000000..ee4cc4a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim
@@ -0,0 +1,321 @@
+vim9script
+
+import './util.vim'
+
+# sort the list of edit operations in the descending order of line and column
+# numbers.
+# 'a': {'A': [lnum, col], 'B': [lnum, col]}
+# 'b': {'A': [lnum, col], 'B': [lnum, col]}
+def Edit_sort_func(a: dict<any>, b: dict<any>): number
+ # line number
+ if a.A[0] != b.A[0]
+ return b.A[0] - a.A[0]
+ endif
+ # column number
+ if a.A[1] != b.A[1]
+ return b.A[1] - a.A[1]
+ endif
+
+ # Assume that the LSP sorted the lines correctly to begin with
+ return b.idx - a.idx
+enddef
+
+# Replaces text in a range with new text.
+#
+# CAUTION: Changes in-place!
+#
+# 'lines': Original list of strings
+# 'A': Start position; [line, col]
+# 'B': End position [line, col]
+# 'new_lines' A list of strings to replace the original
+#
+# returns the modified 'lines'
+def Set_lines(lines: list<string>, A: list<number>, B: list<number>,
+ new_lines: list<string>): list<string>
+ var i_0: number = A[0]
+
+ # If it extends past the end, truncate it to the end. This is because the
+ # way the LSP describes the range including the last newline is by
+ # specifying a line number after what we would call the last line.
+ var numlines: number = lines->len()
+ var i_n = [B[0], numlines - 1]->min()
+
+ if i_0 < 0 || i_0 >= numlines || i_n < 0 || i_n >= numlines
+ #util.WarnMsg("set_lines: Invalid range, A = " .. A->string()
+ # .. ", B = " .. B->string() .. ", numlines = " .. numlines
+ # .. ", new lines = " .. new_lines->string())
+ var msg = $"set_lines: Invalid range, A = {A->string()}"
+ msg ..= $", B = {B->string()}, numlines = {numlines}"
+ msg ..= $", new lines = {new_lines->string()}"
+ util.WarnMsg(msg)
+ return lines
+ endif
+
+ # save the prefix and suffix text before doing the replacements
+ var prefix: string = ''
+ var suffix: string = lines[i_n][B[1] :]
+ if A[1] > 0
+ prefix = lines[i_0][0 : A[1] - 1]
+ endif
+
+ var new_lines_len: number = new_lines->len()
+
+ #echomsg $"i_0 = {i_0}, i_n = {i_n}, new_lines = {string(new_lines)}"
+ var n: number = i_n - i_0 + 1
+ if n != new_lines_len
+ if n > new_lines_len
+ # remove the deleted lines
+ lines->remove(i_0, i_0 + n - new_lines_len - 1)
+ else
+ # add empty lines for newly the added lines (will be replaced with the
+ # actual lines below)
+ lines->extend(repeat([''], new_lines_len - n), i_0)
+ endif
+ endif
+ #echomsg $"lines(1) = {string(lines)}"
+
+ # replace the previous lines with the new lines
+ for i in new_lines_len->range()
+ lines[i_0 + i] = new_lines[i]
+ endfor
+ #echomsg $"lines(2) = {string(lines)}"
+
+ # append the suffix (if any) to the last line
+ if suffix != ''
+ var i = i_0 + new_lines_len - 1
+ lines[i] = lines[i] .. suffix
+ endif
+ #echomsg $"lines(3) = {string(lines)}"
+
+ # prepend the prefix (if any) to the first line
+ if prefix != ''
+ lines[i_0] = prefix .. lines[i_0]
+ endif
+ #echomsg $"lines(4) = {string(lines)}"
+
+ return lines
+enddef
+
+# Apply set of text edits to the specified buffer
+# The text edit logic is ported from the Neovim lua implementation
+export def ApplyTextEdits(bnr: number, text_edits: list<dict<any>>): void
+ if text_edits->empty()
+ return
+ endif
+
+ # if the buffer is not loaded, load it and make it a listed buffer
+ :silent! bnr->bufload()
+ setbufvar(bnr, '&buflisted', true)
+
+ var start_line: number = 4294967295 # 2 ^ 32
+ var finish_line: number = -1
+ var updated_edits: list<dict<any>> = []
+ var start_row: number
+ var start_col: number
+ var end_row: number
+ var end_col: number
+
+ # create a list of buffer positions where the edits have to be applied.
+ var idx = 0
+ for e in text_edits
+ # Adjust the start and end columns for multibyte characters
+ var r = e.range
+ var rstart: dict<any> = r.start
+ var rend: dict<any> = r.end
+ start_row = rstart.line
+ start_col = util.GetCharIdxWithoutCompChar(bnr, rstart)
+ end_row = rend.line
+ end_col = util.GetCharIdxWithoutCompChar(bnr, rend)
+ start_line = [rstart.line, start_line]->min()
+ finish_line = [rend.line, finish_line]->max()
+
+ updated_edits->add({A: [start_row, start_col],
+ B: [end_row, end_col],
+ idx: idx,
+ lines: e.newText->split("\n", true)})
+ idx += 1
+ endfor
+
+ # Reverse sort the edit operations by descending line and column numbers so
+ # that they can be applied without interfering with each other.
+ updated_edits->sort('Edit_sort_func')
+
+ var lines: list<string> = bnr->getbufline(start_line + 1, finish_line + 1)
+ var fix_eol: bool = bnr->getbufvar('&fixeol')
+ var set_eol = fix_eol && bnr->getbufinfo()[0].linecount <= finish_line + 1
+ if !lines->empty() && set_eol && lines[-1]->len() != 0
+ lines->add('')
+ endif
+
+ #echomsg $'lines(1) = {string(lines)}'
+ #echomsg updated_edits
+
+ for e in updated_edits
+ var A: list<number> = [e.A[0] - start_line, e.A[1]]
+ var B: list<number> = [e.B[0] - start_line, e.B[1]]
+ lines = Set_lines(lines, A, B, e.lines)
+ endfor
+
+ #echomsg $'lines(2) = {string(lines)}'
+
+ # If the last line is empty and we need to set EOL, then remove it.
+ if !lines->empty() && set_eol && lines[-1]->len() == 0
+ lines->remove(-1)
+ endif
+
+ #echomsg $'ApplyTextEdits: start_line = {start_line}, finish_line = {finish_line}'
+ #echomsg $'lines = {string(lines)}'
+
+ # if the buffer is empty, appending lines before the first line adds an
+ # extra empty line at the end. Delete the empty line after appending the
+ # lines.
+ var dellastline: bool = false
+ if start_line == 0 && bnr->getbufinfo()[0].linecount == 1 &&
+ bnr->getbufline(1)->get(0, '')->empty()
+ dellastline = true
+ endif
+
+ # Now we apply the textedits to the actual buffer.
+ # In theory we could just delete all old lines and append the new lines.
+ # This would however cause the cursor to change position: It will always be
+ # on the last line added.
+ #
+ # Luckily there is an even simpler solution, that has no cursor sideeffects.
+ #
+ # Logically this method is split into the following three cases:
+ #
+ # 1. The number of new lines is equal to the number of old lines:
+ # Just replace the lines inline with setbufline()
+ #
+ # 2. The number of new lines is greater than the old ones:
+ # First append the missing lines at the **end** of the range, then use
+ # setbufline() again. This does not cause the cursor to change position.
+ #
+ # 3. The number of new lines is less than before:
+ # First use setbufline() to replace the lines that we can replace.
+ # Then remove superfluous lines.
+ #
+ # Luckily, the three different cases exist only logically, we can reduce
+ # them to a single case practically, because appendbufline() does not append
+ # anything if an empty list is passed just like deletebufline() does not
+ # delete anything, if the last line of the range is before the first line.
+ # We just need to be careful with all indices.
+ appendbufline(bnr, finish_line + 1, lines[finish_line - start_line + 1 : -1])
+ setbufline(bnr, start_line + 1, lines)
+ deletebufline(bnr, start_line + 1 + lines->len(), finish_line + 1)
+
+ if dellastline
+ bnr->deletebufline(bnr->getbufinfo()[0].linecount)
+ endif
+enddef
+
+# interface TextDocumentEdit
+def ApplyTextDocumentEdit(textDocEdit: dict<any>)
+ var bnr: number = util.LspUriToBufnr(textDocEdit.textDocument.uri)
+ if bnr == -1
+ util.ErrMsg($'Text Document edit, buffer {textDocEdit.textDocument.uri} is not found')
+ return
+ endif
+ ApplyTextEdits(bnr, textDocEdit.edits)
+enddef
+
+# interface CreateFile
+# Create the "createFile.uri" file
+def FileCreate(createFile: dict<any>)
+ var fname: string = util.LspUriToFile(createFile.uri)
+ var opts: dict<bool> = createFile->get('options', {})
+ var ignoreIfExists: bool = opts->get('ignoreIfExists', true)
+ var overwrite: bool = opts->get('overwrite', false)
+
+ # LSP Spec: Overwrite wins over `ignoreIfExists`
+ if fname->filereadable() && ignoreIfExists && !overwrite
+ return
+ endif
+
+ fname->fnamemodify(':p:h')->mkdir('p')
+ []->writefile(fname)
+ fname->bufadd()
+enddef
+
+# interface DeleteFile
+# Delete the "deleteFile.uri" file
+def FileDelete(deleteFile: dict<any>)
+ var fname: string = util.LspUriToFile(deleteFile.uri)
+ var opts: dict<bool> = deleteFile->get('options', {})
+ var recursive: bool = opts->get('recursive', false)
+ var ignoreIfNotExists: bool = opts->get('ignoreIfNotExists', true)
+
+ if !fname->filereadable() && ignoreIfNotExists
+ return
+ endif
+
+ var flags: string = ''
+ if recursive
+ # # NOTE: is this a dangerous operation? The LSP server can send a
+ # # DeleteFile message to recursively delete all the files in the disk.
+ # flags = 'rf'
+ util.ErrMsg($'Recursively deleting files is not supported')
+ return
+ elseif fname->isdirectory()
+ flags = 'd'
+ endif
+ var bnr: number = fname->bufadd()
+ fname->delete(flags)
+ exe $'{bnr}bwipe!'
+enddef
+
+# interface RenameFile
+# Rename file "renameFile.oldUri" to "renameFile.newUri"
+def FileRename(renameFile: dict<any>)
+ var old_fname: string = util.LspUriToFile(renameFile.oldUri)
+ var new_fname: string = util.LspUriToFile(renameFile.newUri)
+
+ var opts: dict<bool> = renameFile->get('options', {})
+ var overwrite: bool = opts->get('overwrite', false)
+ var ignoreIfExists: bool = opts->get('ignoreIfExists', true)
+
+ if new_fname->filereadable() && (!overwrite || ignoreIfExists)
+ return
+ endif
+
+ old_fname->rename(new_fname)
+enddef
+
+# interface WorkspaceEdit
+export def ApplyWorkspaceEdit(workspaceEdit: dict<any>)
+ if workspaceEdit->has_key('documentChanges')
+ for change in workspaceEdit.documentChanges
+ if change->has_key('kind')
+ if change.kind == 'create'
+ FileCreate(change)
+ elseif change.kind == 'delete'
+ FileDelete(change)
+ elseif change.kind == 'rename'
+ FileRename(change)
+ else
+ util.ErrMsg($'Unsupported change in workspace edit [{change.kind}]')
+ endif
+ else
+ ApplyTextDocumentEdit(change)
+ endif
+ endfor
+ return
+ endif
+
+ if !workspaceEdit->has_key('changes')
+ return
+ endif
+
+ for [uri, changes] in workspaceEdit.changes->items()
+ var bnr: number = util.LspUriToBufnr(uri)
+ if bnr == 0
+ # file is not present
+ continue
+ endif
+
+ # interface TextEdit
+ ApplyTextEdits(bnr, changes)
+ endfor
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim
new file mode 100644
index 0000000..8786b1b
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim
@@ -0,0 +1,174 @@
+vim9script
+
+# Functions for dealing with type hierarchy (super types/sub types)
+
+import './util.vim'
+import './symbol.vim'
+
+# Parse the type hierarchy in "typeHier" and displays a tree of type names
+# in the current buffer. This function is called recursively to display the
+# super/sub type hierarchy.
+#
+# Returns the line number where the next type name should be added.
+def TypeTreeGenerate(isSuper: bool, typeHier: dict<any>, pfx_arg: string,
+ typeTree: list<string>, typeUriMap: list<dict<any>>)
+
+ var itemHasChildren = false
+ if isSuper
+ if typeHier->has_key('parents') && !typeHier.parents->empty()
+ itemHasChildren = true
+ endif
+ else
+ if typeHier->has_key('children') && !typeHier.children->empty()
+ itemHasChildren = true
+ endif
+ endif
+
+ var itemBranchPfx: string
+ if itemHasChildren
+ itemBranchPfx = '▾ '
+ else
+ itemBranchPfx = pfx_arg->empty() ? '' : ' '
+ endif
+
+ var typestr: string
+ var kindstr = symbol.SymbolKindToName(typeHier.kind)
+ if kindstr != ''
+ typestr = $'{pfx_arg}{itemBranchPfx}{typeHier.name} ({kindstr[0]})'
+ else
+ typestr = $'{pfx_arg}{itemBranchPfx}{typeHier.name}'
+ endif
+ typeTree->add(typestr)
+ typeUriMap->add(typeHier)
+
+ # last item to process
+ if !itemHasChildren
+ return
+ endif
+
+ var items: list<dict<any>>
+ items = isSuper ? typeHier.parents : typeHier.children
+
+ for item in items
+ TypeTreeGenerate(isSuper, item, $'{pfx_arg}| ', typeTree, typeUriMap)
+ endfor
+enddef
+
+# Display a popup with the file containing a type and highlight the line and
+# the type name.
+def UpdateTypeHierFileInPopup(lspserver: dict<any>, typeUriMap: list<dict<any>>)
+ if lspserver.typeHierPopup->winbufnr() == -1
+ return
+ endif
+
+ lspserver.typeHierFilePopup->popup_close()
+
+ var n = line('.', lspserver.typeHierPopup) - 1
+ var fname: string = util.LspUriToFile(typeUriMap[n].uri)
+
+ var bnr = fname->bufadd()
+ if bnr == 0
+ return
+ endif
+
+ var popupAttrs = {
+ title: $"{fname->fnamemodify(':t')} ({fname->fnamemodify(':h')})",
+ wrap: false,
+ fixed: true,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: winwidth(0) - 38,
+ maxwidth: winwidth(0) - 38,
+ cursorline: true,
+ border: [],
+ line: 'cursor+1',
+ col: 1
+ }
+ lspserver.typeHierFilePopup = popup_create(bnr, popupAttrs)
+ var cmds =<< trim eval END
+ [{typeUriMap[n].range.start.line + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(lspserver.typeHierFilePopup, cmds)
+
+ lspserver.typeHierFilePopup->clearmatches()
+ var start_col = util.GetLineByteFromPos(bnr,
+ typeUriMap[n].selectionRange.start) + 1
+ var end_col = util.GetLineByteFromPos(bnr, typeUriMap[n].selectionRange.end)
+ var pos = [[typeUriMap[n].selectionRange.start.line + 1,
+ start_col, end_col - start_col + 1]]
+ matchaddpos('Search', pos, 10, -1, {window: lspserver.typeHierFilePopup})
+enddef
+
+def TypeHierPopupFilter(lspserver: dict<any>, typeUriMap: list<dict<any>>,
+ popupID: number, key: string): bool
+ popupID->popup_filter_menu(key)
+ if lspserver.typeHierPopup->winbufnr() == -1
+ # popup is closed
+ if lspserver.typeHierFilePopup->winbufnr() != -1
+ lspserver.typeHierFilePopup->popup_close()
+ endif
+ lspserver.typeHierFilePopup = -1
+ lspserver.typeHierPopup = -1
+ else
+ UpdateTypeHierFileInPopup(lspserver, typeUriMap)
+ endif
+
+ return true
+enddef
+
+def TypeHierPopupCallback(lspserver: dict<any>, typeUriMap: list<dict<any>>,
+ popupID: number, selIdx: number)
+ if lspserver.typeHierFilePopup->winbufnr() != -1
+ lspserver.typeHierFilePopup->popup_close()
+ endif
+ lspserver.typeHierFilePopup = -1
+ lspserver.typeHierPopup = -1
+
+ if selIdx <= 0
+ # popup is canceled
+ return
+ endif
+
+ # Save the current cursor location in the tag stack.
+ util.PushCursorToTagStack()
+ util.JumpToLspLocation(typeUriMap[selIdx - 1], '')
+enddef
+
+# Show the super or sub type hierarchy items "types" as a tree in a popup
+# window
+export def ShowTypeHierarchy(lspserver: dict<any>, isSuper: bool, types: dict<any>)
+
+ if lspserver.typeHierPopup->winbufnr() != -1
+ # If the type hierarchy popup window is already present, close it.
+ lspserver.typeHierPopup->popup_close()
+ endif
+
+ var typeTree: list<string>
+ var typeUriMap: list<dict<any>>
+
+ # Generate a tree of the type hierarchy items
+ TypeTreeGenerate(isSuper, types, '', typeTree, typeUriMap)
+
+ # Display a popup window with the type hierarchy tree and a popup window for
+ # the file.
+ var popupAttrs = {
+ title: $'{isSuper ? "Super" : "Sub"}Type Hierarchy',
+ wrap: 0,
+ pos: 'topleft',
+ line: 'cursor+1',
+ col: winwidth(0) - 34,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: 30,
+ maxwidth: 30,
+ mapping: false,
+ fixed: true,
+ filter: function(TypeHierPopupFilter, [lspserver, typeUriMap]),
+ callback: function(TypeHierPopupCallback, [lspserver, typeUriMap])
+ }
+ lspserver.typeHierPopup = popup_menu(typeTree, popupAttrs)
+ UpdateTypeHierFileInPopup(lspserver, typeUriMap)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/util.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/util.vim
new file mode 100644
index 0000000..eb0a64f
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/util.vim
@@ -0,0 +1,364 @@
+vim9script
+
+# Display an info message
+export def InfoMsg(msg: string)
+ :echohl Question
+ :echomsg $'Info: {msg}'
+ :echohl None
+enddef
+
+# Display a warning message
+export def WarnMsg(msg: string)
+ :echohl WarningMsg
+ :echomsg $'Warn: {msg}'
+ :echohl None
+enddef
+
+# Display an error message
+export def ErrMsg(msg: string)
+ :echohl Error
+ :echomsg $'Error: {msg}'
+ :echohl None
+enddef
+
+# Lsp server trace log directory
+var lsp_log_dir: string
+if has('unix')
+ lsp_log_dir = '/tmp/'
+else
+ lsp_log_dir = $TEMP .. '\\'
+endif
+
+# Log a message from the LSP server. stderr is true for logging messages
+# from the standard error and false for stdout.
+export def TraceLog(fname: string, stderr: bool, msg: string)
+ if stderr
+ writefile(msg->split("\n"), $'{lsp_log_dir}{fname}', 'a')
+ else
+ writefile([msg], $'{lsp_log_dir}{fname}', 'a')
+ endif
+enddef
+
+# Empty out the LSP server trace logs
+export def ClearTraceLogs(fname: string)
+ writefile([], $'{lsp_log_dir}{fname}')
+enddef
+
+# Open the LSP server debug messages file.
+export def ServerMessagesShow(fname: string)
+ var fullname = $'{lsp_log_dir}{fname}'
+ if !filereadable(fullname)
+ WarnMsg($'File {fullname} is not found')
+ return
+ endif
+ var wid = fullname->bufwinid()
+ if wid == -1
+ exe $'split {fullname}'
+ else
+ win_gotoid(wid)
+ endif
+ setlocal autoread
+ setlocal bufhidden=wipe
+ setlocal nomodified
+ setlocal nomodifiable
+enddef
+
+# Parse a LSP Location or LocationLink type and return a List with two items.
+# The first item is the DocumentURI and the second item is the Range.
+export def LspLocationParse(lsploc: dict<any>): list<any>
+ if lsploc->has_key('targetUri')
+ # LocationLink
+ return [lsploc.targetUri, lsploc.targetSelectionRange]
+ else
+ # Location
+ return [lsploc.uri, lsploc.range]
+ endif
+enddef
+
+# Convert a LSP file URI (file://<absolute_path>) to a Vim file name
+export def LspUriToFile(uri: string): string
+ # Replace all the %xx numbers (e.g. %20 for space) in the URI to character
+ var uri_decoded: string = substitute(uri, '%\(\x\x\)',
+ '\=nr2char(str2nr(submatch(1), 16))', 'g')
+
+ # File URIs on MS-Windows start with file:///[a-zA-Z]:'
+ if uri_decoded =~? '^file:///\a:'
+ # MS-Windows URI
+ uri_decoded = uri_decoded[8 : ]
+ uri_decoded = uri_decoded->substitute('/', '\\', 'g')
+ # On GNU/Linux (pattern not end with `:`)
+ elseif uri_decoded =~? '^file:///\a'
+ uri_decoded = uri_decoded[7 : ]
+ endif
+
+ return uri_decoded
+enddef
+
+# Convert a LSP file URI (file://<absolute_path>) to a Vim buffer number.
+# If the file is not in a Vim buffer, then adds the buffer.
+# Returns 0 on error.
+export def LspUriToBufnr(uri: string): number
+ return LspUriToFile(uri)->bufadd()
+enddef
+
+# Returns if the URI refers to a remote file (e.g. ssh://)
+# Credit: vim-lsp plugin
+export def LspUriRemote(uri: string): bool
+ return uri =~ '^\w\+::' || uri =~ '^[a-z][a-z0-9+.-]*://'
+enddef
+
+var resolvedUris = {}
+
+# Convert a Vim filename to an LSP URI (file://<absolute_path>)
+export def LspFileToUri(fname: string): string
+ var fname_full: string = fname->fnamemodify(':p')
+
+ if resolvedUris->has_key(fname_full)
+ return resolvedUris[fname_full]
+ endif
+
+ var uri: string = fname_full
+
+ if has("win32unix")
+ # We're in Cygwin, convert POSIX style paths to Windows style.
+ # The substitution is to remove the '^@' escape character from the end of
+ # line.
+ uri = system($'cygpath -m {uri}')->substitute('^\(\p*\).*$', '\=submatch(1)', "")
+ endif
+
+ var on_windows: bool = false
+ if uri =~? '^\a:'
+ on_windows = true
+ endif
+
+ if on_windows
+ # MS-Windows
+ uri = uri->substitute('\\', '/', 'g')
+ endif
+
+ uri = uri->substitute('\([^A-Za-z0-9-._~:/]\)',
+ '\=printf("%%%02x", char2nr(submatch(1)))', 'g')
+
+ if on_windows
+ uri = $'file:///{uri}'
+ else
+ uri = $'file://{uri}'
+ endif
+
+ resolvedUris[fname_full] = uri
+ return uri
+enddef
+
+# Convert a Vim buffer number to an LSP URI (file://<absolute_path>)
+export def LspBufnrToUri(bnr: number): string
+ return LspFileToUri(bnr->bufname())
+enddef
+
+# Returns the byte number of the specified LSP position in buffer "bnr".
+# LSP's line and characters are 0-indexed.
+# Vim's line and columns are 1-indexed.
+# Returns a zero-indexed column.
+export def GetLineByteFromPos(bnr: number, pos: dict<number>): number
+ var col: number = pos.character
+ # When on the first character, we can ignore the difference between byte and
+ # character
+ if col <= 0
+ return col
+ endif
+
+ # Need a loaded buffer to read the line and compute the offset
+ :silent! bnr->bufload()
+
+ var ltext: string = bnr->getbufline(pos.line + 1)->get(0, '')
+ if ltext->empty()
+ return col
+ endif
+
+ var byteIdx = ltext->byteidxcomp(col)
+ if byteIdx != -1
+ return byteIdx
+ endif
+
+ return col
+enddef
+
+# Get the index of the character at [pos.line, pos.character] in buffer "bnr"
+# without counting the composing characters. The LSP server counts composing
+# characters as separate characters whereas Vim string indexing ignores the
+# composing characters.
+export def GetCharIdxWithoutCompChar(bnr: number, pos: dict<number>): number
+ var col: number = pos.character
+ # When on the first character, nothing to do.
+ if col <= 0
+ return col
+ endif
+
+ # Need a loaded buffer to read the line and compute the offset
+ :silent! bnr->bufload()
+
+ var ltext: string = bnr->getbufline(pos.line + 1)->get(0, '')
+ if ltext->empty()
+ return col
+ endif
+
+ # Convert the character index that includes composing characters as separate
+ # characters to a byte index and then back to a character index ignoring the
+ # composing characters.
+ var byteIdx = ltext->byteidxcomp(col)
+ if byteIdx != -1
+ if byteIdx == ltext->strlen()
+ # Byte index points to the byte after the last byte.
+ return ltext->strcharlen()
+ else
+ return ltext->charidx(byteIdx, false)
+ endif
+ endif
+
+ return col
+enddef
+
+# Get the index of the character at [pos.line, pos.character] in buffer "bnr"
+# counting the composing characters as separate characters. The LSP server
+# counts composing characters as separate characters whereas Vim string
+# indexing ignores the composing characters.
+export def GetCharIdxWithCompChar(ltext: string, charIdx: number): number
+ # When on the first character, nothing to do.
+ if charIdx <= 0 || ltext->empty()
+ return charIdx
+ endif
+
+ # Convert the character index that doesn't include composing characters as
+ # separate characters to a byte index and then back to a character index
+ # that includes the composing characters as separate characters
+ var byteIdx = ltext->byteidx(charIdx)
+ if byteIdx != -1
+ if byteIdx == ltext->strlen()
+ return ltext->strchars()
+ else
+ return ltext->charidx(byteIdx, true)
+ endif
+ endif
+
+ return charIdx
+enddef
+
+# push the current location on to the tag stack
+export def PushCursorToTagStack()
+ settagstack(winnr(), {items: [
+ {
+ bufnr: bufnr(),
+ from: getpos('.'),
+ matchnr: 1,
+ tagname: expand('<cword>')
+ }]}, 't')
+enddef
+
+# Jump to the LSP "location". The "location" contains the file name, line
+# number and character number. The user specified window command modifiers
+# (e.g. topleft) are in "cmdmods".
+export def JumpToLspLocation(location: dict<any>, cmdmods: string)
+ var [uri, range] = LspLocationParse(location)
+ var fname = LspUriToFile(uri)
+
+ # jump to the file and line containing the symbol
+ var bnr: number = fname->bufnr()
+ if cmdmods->empty()
+ if bnr == bufnr()
+ # Set the previous cursor location mark. Instead of using setpos(), m' is
+ # used so that the current location is added to the jump list.
+ :normal m'
+ else
+ var wid = fname->bufwinid()
+ if wid != -1
+ wid->win_gotoid()
+ else
+ if bnr != -1
+ # Reuse an existing buffer. If the current buffer has unsaved changes
+ # and 'hidden' is not set or if the current buffer is a special
+ # buffer, then open the buffer in a new window.
+ if (&modified && !&hidden) || &buftype != ''
+ exe $'belowright sbuffer {bnr}'
+ else
+ exe $'buf {bnr}'
+ endif
+ else
+ if (&modified && !&hidden) || &buftype != ''
+ # if the current buffer has unsaved changes and 'hidden' is not set,
+ # or if the current buffer is a special buffer, then open the file
+ # in a new window
+ exe $'belowright split {fname}'
+ else
+ exe $'edit {fname}'
+ endif
+ endif
+ endif
+ endif
+ else
+ if bnr == -1
+ exe $'{cmdmods} split {fname}'
+ else
+ # Use "sbuffer" so that the 'switchbuf' option settings are used.
+ exe $'{cmdmods} sbuffer {bnr}'
+ endif
+ endif
+ var rstart = range.start
+ setcursorcharpos(rstart.line + 1,
+ GetCharIdxWithoutCompChar(bufnr(), rstart) + 1)
+ :normal! zv
+enddef
+
+# indexof() function is not present in older Vim 9 versions. So use this
+# function.
+export def Indexof(list: list<any>, CallbackFn: func(number, any): bool): number
+ var ix = 0
+ for val in list
+ if CallbackFn(ix, val)
+ return ix
+ endif
+ ix += 1
+ endfor
+ return -1
+enddef
+
+# Find the nearest root directory containing a file or directory name from the
+# list of names in "files" starting with the directory "startDir".
+# Based on a similar implementation in the vim-lsp plugin.
+# Searches upwards starting with the directory "startDir".
+# If a file name ends with '/' or '\', then it is a directory name, otherwise
+# it is a file name.
+# Returns '' if none of the file and directory names in "files" can be found
+# in one of the parent directories.
+export def FindNearestRootDir(startDir: string, files: list<any>): string
+ var foundDirs: dict<bool> = {}
+
+ for file in files
+ if file->type() != v:t_string || file->empty()
+ continue
+ endif
+ var isDir = file[-1 : ] == '/' || file[-1 : ] == '\'
+ var relPath: string
+ if isDir
+ relPath = finddir(file, $'{startDir};')
+ else
+ relPath = findfile(file, $'{startDir};')
+ endif
+ if relPath->empty()
+ continue
+ endif
+ var rootDir = relPath->fnamemodify(isDir ? ':p:h:h' : ':p:h')
+ foundDirs[rootDir] = true
+ endfor
+ if foundDirs->empty()
+ return ''
+ endif
+
+ # Sort the directory names by length
+ var sortedList: list<string> = foundDirs->keys()->sort((a, b) => {
+ return b->len() - a->len()
+ })
+
+ # choose the longest matching path (the nearest directory from "startDir")
+ return sortedList[0]
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/doc/lsp.txt b/vim/pack/downloads/opt/lsp/doc/lsp.txt
new file mode 100644
index 0000000..0d39a35
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/doc/lsp.txt
@@ -0,0 +1,2001 @@
+*lsp.txt* Language Server Protocol (LSP) Plugin for Vim9
+
+
+Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com)
+For Vim version 9.0 and above
+Last change: Feb 13, 2024
+
+==============================================================================
+CONTENTS *lsp-contents*
+
+ 1. Overview ................................. |lsp-overview|
+ 2. Requirements ............................. |lsp-installation|
+ 3. Usage .................................... |lsp-usage|
+ 4. Configuration............................. |lsp-configuration|
+ 5. Commands ................................. |lsp-commands|
+ 6. Insert Mode Completion ................... |lsp-ins-mode-completion|
+ 7. Diagnostics .............................. |lsp-diagnostics|
+ 8. Tag Function ............................. |lsp-tagfunc|
+ 9. LSP Formatting ........................... |lsp-format|
+ 10. Call Hierarchy ........................... |lsp-call-hierarchy|
+ 11. Autocommands ............................. |lsp-autocmds|
+ 12. Highlight Groups ......................... |lsp-highlight-groups|
+ 13. Debugging ................................ |lsp-debug|
+ 14. Custom Command Handlers .................. |lsp-custom-commands|
+ 15. Custom LSP Completion Kinds .............. |lsp-custom-kinds|
+ 16. Multiple Language Servers for a buffer ... |lsp-multiple-servers|
+ 17. Language Servers Features ................ |lsp-features|
+ 18. License .................................. |lsp-license|
+
+==============================================================================
+1. Overview *lsp-overview*
+
+The Language Server Protocol (LSP) plugin implements a LSP client for Vim9.
+Refer to the following pages for more information about LSP:
+
+ https://microsoft.github.io/language-server-protocol/
+ https://langserver.org/
+
+This plugin needs Vim version 9.0 and after. You will need a programming
+language specific server in your system to use this plugin. Refer to the above
+pages for a list of available language servers for the various programming
+languages.
+
+The Github repository for this plugin is available at:
+
+ http://github.com/yegappan/lsp
+
+==============================================================================
+2. Installation *lsp-installation*
+
+You can install this plugin directly from github using the following steps:
+
+ $ mkdir -p $HOME/.vim/pack/downloads/opt
+ $ cd $HOME/.vim/pack/downloads/opt
+ $ git clone https://github.com/yegappan/lsp
+ $ vim -u NONE -c "helptags $HOME/.vim/pack/downloads/opt/lsp/doc" -c q
+
+or you can use any one of the Vim plugin managers (dein.vim, pathogen, vam,
+vim-plug, volt, Vundle, etc.) to install and manage this plugin.
+
+To uninstall the LSP plugin, either use the uninstall command provided by the
+plugin manager or manually remove the $HOME/.vim/pack/downloads/lsp directory.
+
+To use this plugin, add the following line to your .vimrc file:
+
+ packadd lsp
+
+==============================================================================
+3. Usage *lsp-usage*
+
+The following commands are provided:
+
+:LspCodeAction Apply the code action supplied by the language server
+ to the diagnostic in the current line.
+:LspCodeLens Display all the code lens commands available for the
+ current file and apply the selected command.
+:LspDiag current Display the diagnostic message for the current line.
+:LspDiag first Jump to the first diagnostic message for the current
+ buffer.
+:LspDiag here Jump to the next diagnostic message in the current
+ line.
+:LspDiag highlight disable
+ Disable highlighting lines with a diagnostic message
+ for the current Vim session.
+:LspDiag highlight enable
+ Enable highlighting lines with a diagnostic message
+ for the current Vim session.
+:LspDiag last Jump to the last diagnostic message for the current
+ buffer.
+:LspDiag next Jump to the next diagnostic message for the current
+ buffer after the current cursor position.
+:LspDiag nextWrap Jump to the next diagnostic message for the current
+ buffer after the current cursor position.
+ Wrap back to the first message when no more messages
+ are found.
+:LspDiag prev Jump to the previous diagnostic message for the
+ current buffer before the current current position.
+:LspDiag prevWrap Jump to the previous diagnostic message for the
+ current buffer before the current current position.
+ Wrap back to the last message when no previous
+ messages are found.
+:LspDiag show Display the diagnostics messages from the language
+ server for the current buffer in a location list.
+:LspDocumentSymbol Display the symbols in the current file in a popup
+ menu and jump to the location of a selected symbol.
+:LspFold Fold the current file
+:LspFormat Format a range of lines in the current file using the
+ language server. The default range is the entire
+ file. See |lsp-format| for more information.
+:LspGotoDeclaration Go to the declaration of the symbol under cursor
+:LspGotoDefinition Go to the definition of the symbol under cursor
+:LspGotoImpl Go to the implementation of the symbol under cursor
+:LspGotoTypeDef Go to the type definition of the symbol under cursor
+:LspHighlight Highlight all the matches for the keyword under cursor
+:LspHighlightClear Clear all the matches highlighted by :LspHighlight
+:LspHover Show the documentation for the symbol under the cursor
+ in a popup window.
+:LspIncomingCalls Display the list of symbols calling the current symbol
+ in a window.
+:LspInlayHints Enable or disable inlay hints.
+:LspOutgoingCalls Display the list of symbols called by the current
+ symbol in a window.
+:LspOutline Show the list of symbols defined in the current file
+ in a separate window.
+:LspPeekDeclaration Open the declaration of the symbol under cursor in a
+ popup window.
+:LspPeekDefinition Open the definition of the symbol under cursor in a
+ popup window.
+:LspPeekImpl Open the implementation of the symbol under cursor in
+ a popup window.
+:LspPeekReferences Display the list of references to the symbol under
+ cursor in a popup window.
+:LspPeekTypeDef Open the type definition of the symbol under cursor in
+ a popup window.
+:LspRename Rename the current symbol
+:LspSelectionExpand Expand the current symbol range visual selection
+:LspSelectionShrink Shrink the current symbol range visual selection
+:LspServer Command to display the status and messages from a
+ language server and to restart the language server.
+:LspShowAllServers Display the status of all the registered language
+ servers.
+:LspShowReferences Display the list of references to the keyword under
+ cursor in a new location list.
+:LspShowSignature Display the signature of the symbol under cursor.
+:LspSubTypeHierarchy Display the sub type hierarchy in a popup window.
+:LspSuperTypeHierarchy Display the super type hierarchy in a popup window.
+:LspSwitchSourceHeader Switch between a source and a header file.
+:LspSymbolSearch Perform a workspace wide search for a symbol
+:LspWorkspaceAddFolder {folder}
+ Add a folder to the workspace
+:LspWorkspaceListFolders
+ Show the list of folders in the workspace
+:LspWorkspaceRemoveFolder {folder}
+ Remove a folder from the workspace
+
+==============================================================================
+4. Configuration *lsp-configuration*
+ *LspAddServer()* *g:LspAddServer()*
+
+To use the plugin features with a particular file type(s), you need to first
+register a language server for that file type(s).
+
+To register one or more language servers, use the LspAddServer() function with
+a list of lanaguge server details in the .vimrc file.
+
+To register a language server, add the following lines to your .vimrc file
+(use only the language servers that you need from the below list).
+If you used [vim-plug](https://github.com/junegunn/vim-plug) to install the
+LSP plugin, the steps are described later in this section: >
+
+ vim9script
+ var lspServers = [
+ {
+ name: 'typescriptls',
+ filetype: ['javascript', 'typescript'],
+ path: '/usr/local/bin/typescript-language-server',
+ args: ['--stdio']
+ },
+ {
+ name: 'pythonls',
+ filetype: 'python',
+ path: '/usr/local/bin/pyls',
+ args: ['--check-parent-process', '-v']
+ }
+ ]
+ LspAddServer(lspServers)
+<
+Depending on the location of the typescript and python pyls language servers
+installed in your system, update the "path" in the above snippet
+appropriately.
+
+Another example, for adding the language servers for the C, C++, Golang, Rust,
+Shell script, Vim script and PHP file types: >
+
+ vim9script
+ var lspServers = [
+ {
+ name: 'clangd',
+ filetype: ['c', 'cpp'],
+ path: '/usr/local/bin/clangd',
+ args: ['--background-index']
+ },
+ {
+ name: 'golang',
+ filetype: ['go', 'gomod', 'gohtmltmpl', 'gotexttmpl'],
+ path: '/path/to/.go/bin/gopls',
+ args: [],
+ syncInit: true,
+ },
+ {
+ name: 'rustls',
+ filetype: ['rust'],
+ path: '/path/to/.cargo/bin/rust-analyzer',
+ args: [],
+ syncInit: true,
+ },
+ {
+ name: 'bashls',
+ filetype: 'sh',
+ path: '/usr/local/bin/bash-language-server',
+ args: ['start']
+ },
+ {
+ name: 'vimls',
+ filetype: ['vim'],
+ path: '/usr/local/bin/vim-language-server',
+ args: ['--stdio']
+ },
+ {
+ name: 'phpls',
+ filetype: ['php'],
+ path': '/usr/local/bin/intelephense',
+ args: ['--stdio'],
+ syncInit: true,
+ initializationOptions: {
+ licenceKey: 'xxxxxxxxxxxxxxx'
+ }
+ }
+ ]
+ LspAddServer(lspServers)
+<
+To add a language server, the following information is needed:
+
+ *lsp-cfg-name*
+ name (Optional) name of the language server. Can by any
+ string. Used in LSP messages and log files.
+ *lsp-cfg-path*
+ path complete path to the language server executable
+ (without any arguments).
+ *lsp-cfg-args*
+ args a |List| of command-line arguments passed to the
+ language server. Each space separated language server
+ command-line argument is a separate List item.
+ *lsp-cfg-filetype*
+ filetype One or more file types supported by the language
+ server. This can be a |String| or a |List|. To
+ specify multiple file types, use a List.
+ *lsp-cfg-initializationOptions*
+ initializationOptions
+ (Optional) for lsp servers (e.g. intelephense) some
+ additional initialization options may be required
+ or useful for initialization. Those can be provided in
+ this dictionary and if present will be transmitted to
+ the lsp server.
+ *lsp-cfg-workspaceConfig*
+ workspaceConfig (Optional) a json encodable value that will be sent to
+ the language server after initialization as the
+ "settings" in a "workspace/didChangeConfiguration"
+ notification. Refer to the language server
+ documentation for the values that will be accepted in
+ this notification. This configuration is also used to
+ respond to the "workspace/configuration" request
+ message from the language server.
+ *lsp-cfg-rootSearch*
+ rootSearch (Optional) a List of file and directory names used to
+ locate the root path or uri of the workspace. The
+ directory names in "rootSearch" must end in "/" or
+ "\". Each file and directory name in "rootSearch" is
+ searched upwards in all the parent directories. If
+ multiple directories are found, then the directory
+ closest to the directory of the current buffer is used
+ as the workspace root.
+
+ If this parameter is not specified or the files are
+ not found, then the current working directory is used
+ as the workspace root for decendent files, for any
+ other files the parent directory of the file is used.
+
+ *lsp-cfg-runIfSearch*
+ runIfSearch (Optional) a List of file and directory names used to
+ determinate if a server should run or not. The
+ directory names in "runIfSearch" must end in "/" or
+ "\". Each file and directory name in "runIfSearch" is
+ searched upwards in all the parent directories.
+ Exactly like |lsp-cfg-rootSearch|.
+
+ If a file or directory is found then the server will
+ be started, otherwise it will not.
+
+ If this parameter is not specified or is an empty
+ list, then the server will be started unless
+ |lsp-cfg-runUnlessSearch| prevents it.
+
+ *lsp-cfg-runUnlessSearch*
+ runUnlessSearch (Optional) Opposite of |lsp-cfg-runIfSearch|.
+
+Additionally the following configurations can be made:
+
+ *lsp-cfg-customNotificationHandlers*
+ customNotificationHandlers
+ (Optional) some lsp servers (e.g.
+ typescript-language-server) will send additional
+ notifications which you might want to silence or
+ handle. The provided notification handlers will be
+ called with a reference to the "lspserver" and the
+ "reply". >
+
+ vim9script
+ g:LspAddServer([{
+ filetype: ['javascript', 'typescript'],
+ path: '/usr/local/bin/typescript-language-server',
+ args: ['--stdio'],
+ customNotificationHandlers: {
+ '$/typescriptVersion': (lspserver, reply) => {
+ echom printf("TypeScript Version = %s",
+ reply.params.version)
+ }
+ }
+ }])
+<
+ *lsp-cfg-customRequestHandlers*
+ customRequestHandlers
+ (Optional) some lsp servers will send additional
+ request replies which you might want to silence or
+ handle. The provided request handlers will be called
+ with a reference to the "lspserver" and the "request".
+
+ features *lsp-cfg-features*
+ (Optional) toggle which features should be enabled for
+ a given language server. See |lsp-multiple-servers|
+ and |lsp-features| for more information.
+
+ forceOffsetEncoding *lsp-cfg-forceOffsetEncoding*
+ (Optional) a |String| value that forces the use of a
+ specific offset encoding in LSP messages. If this
+ option is not specified, then the UTF offset encoding
+ is negotiated with the server during initialization.
+ Supported values are 'utf-8' or 'utf-16' or 'utf-32'.
+ The Vim native offset encoding is 'utf-32'. For the
+ 'utf-8' and 'utf-16' encodings, the offsets need to be
+ encoded and decoded in every LSP message and will
+ incur some overhead.
+
+ *lsp-cfg-omnicompl*
+ omnicompl (Optional) a boolean value that enables (true)
+ or disables (false) omni-completion for these file
+ types. By default this is set to "v:true". This value
+ is applicable only if auto completion is disabled
+ (|lsp-opt-autoComplete|).
+
+ *lsp-cfg-processDiagHandler*
+ processDiagHandler
+ (Optional) A |Funcref| or |lambda| that takes a list
+ of language server diagnostics and returns a new list
+ of filtered, or otherwise changed diagnostics. Can be
+ used to remove unwanted diagnostics, prefix the
+ diagnostics text, etc. The following example will
+ remove all but errors and warnings: >
+
+ vim9script
+ g:LspAddServer([{
+ filetype: ['javascript', 'typescript'],
+ path: '/usr/local/bin/typescript-language-server',
+ args: ['--stdio'],
+ processDiagHandler: (diags: list<dict<any>>) => {
+ # Only include errors and warnings
+ return diags->filter((diag, ix) => {
+ return diag.severity <= 2
+ })
+ },
+ }])
+<
+ And this example will prefix the diagnostic message
+ with the string "TypeScript: ": >
+
+ vim9script
+ g:LspAddServer([{
+ filetype: ['javascript', 'typescript'],
+ path: '/usr/local/bin/typescript-language-server',
+ args: ['--stdio'],
+ processDiagHandler: (diags: list<dict<any>>) => {
+ return diags->map((diag, ix) => {
+ diag.message = $'TypeScript: {diag.message}'
+ return diag
+ })
+ },
+ }])
+<
+ *lsp-cfg-syncInit*
+ syncInit (Optional) for language servers (e.g. rust analyzer,
+ gopls, etc.) that take time to initialize and reply to
+ a "initialize" request message this should be set to
+ "true". If this is set to true, then a synchronous
+ call is used to initialize the language server,
+ otherwise the server is initialized asynchronously.
+ By default this is set to "false".
+
+ *lsp-cfg-debug*
+ debug (Optional) log the messages printed by this language
+ server in stdout and stderr to a file. Useful for
+ debugging a language server. By default the
+ messages are not logged. See |lsp-debug| for more
+ information.
+
+ *lsp-cfg-traceLevel*
+ traceLevel (Optional) set the debug trace level for this language
+ server. Supported values are: "off", "debug" and
+ "verbose". By default this is seto "off".
+
+The language servers are added using the LspAddServer() function. This
+function accepts a list of language servers with the above information.
+
+If you used [vim-plug](https://github.com/junegunn/vim-plug) to install the
+LSP plugin, then you need to use the LspSetup User autocmd to initialize the
+language server and to set the language server options. For example: >
+
+ vim9script
+
+ var lspOpts = {autoHighlightDiags: true}
+ autocmd User LspSetup LspOptionsSet(lspOpts)
+
+ var lspServers = [
+ {
+ name: 'clangd',
+ filetype: ['c', 'cpp'],
+ path: '/usr/local/bin/clangd',
+ args: ['--background-index']
+ }
+ ]
+ autocmd User LspSetup LspAddServer(lspServers)
+<
+ *lsp-options* *LspOptionsSet()*
+ *g:LspOptionsSet()*
+
+Some of the LSP plugin features can be enabled or disabled by using the
+LspOptionsSet() function. This function accepts a dictionary argument with the
+following optional items:
+
+ *lsp-opt-aleSupport*
+aleSupport |Boolean| option. If true, diagnostics will be sent to
+ Ale, instead of being displayed by this plugin.
+ This is useful to combine all LSP and linter
+ diagnostics. By default this is set to false.
+
+ *lsp-opt-autoComplete*
+autoComplete |Boolean| option. In insert mode, automatically
+ complete the current symbol. Otherwise use
+ omni-completion. By default this is set to true.
+
+ *lsp-opt-autoHighlight*
+autoHighlight |Boolean| option. In normal mode, automatically
+ highlight all the occurrences of the symbol under the
+ cursor. By default this is set to false.
+
+ *lsp-opt-autoHighlightDiags*
+autoHighlightDiags |Boolean| option. Automatically place signs on the
+ lines with a diagnostic message from the language
+ server. By default this is set to true.
+
+ *lsp-opt-autoPopulateDiags*
+autoPopulateDiags |Boolean| option. Automatically populate the location
+ list with diagnostics from the language server.
+ By default this is set to false.
+
+ *lsp-opt-completionMatcher*
+completionMatcher |String| option. Enable fuzzy or case insensitive
+ completion for language servers that replies with a
+ full list of completion items. Some language servers
+ does completion filtering in the server, while other
+ relies on the client to do the filtering.
+
+ This option only works for language servers that
+ expect the client to filter the completion items.
+
+ This option accepts one of the following values:
+ case - case sensitive matching (default).
+ fuzzy - fuzzy match completion items.
+ icase - ignore case when matching items.
+
+ *lsp-opt-completionTextEdit*
+completionTextEdit |Boolean| option. If true, apply the LSP server
+ supplied text edits after a completion. If a snippet
+ plugin is going to apply the text edits, then set
+ this to false to avoid applying the text edits twice.
+ By default this is set to true.
+
+ *lsp-opt-completionKinds*
+completionKinds |Dictionary| option. See |lsp-custom-kinds| for all
+ completion kind names.
+
+ *lsp-opt-customCompletionKinds*
+customCompletionKinds |Boolean| option. If you set this to true, you can
+ set custom completion kinds using the option
+ completionKinds.
+
+ *lsp-opt-diagSignErrorText*
+diagSignErrorText |String| option. Change diag sign text for errors
+ By default 'E>'
+
+ *lsp-opt-diagSignHintText*
+diagSignHintText |String| option. Change diag sign text for hints
+ By default 'H>',
+
+ *lsp-opt-diagSignInfoText*
+diagSignInfoText |String| option. Change diag sign text for info
+ By default 'I>',
+
+ *lsp-opt-diagSignWarningText*
+diagSignWarningText |String| option. Change diag sign text for warnings
+ By default 'W>',
+
+ *lsp-opt-diagVirtualTextAlign*
+diagVirtualTextAlign |String| option. Alignment of diagnostics messages
+ if |lsp-opt-showDiagWithVirtualText| is set to true.
+ Allowed values are 'above', 'below' or 'after'
+ By default this is set to 'above',
+
+ *lsp-opt-diagVirtualTextWrap*
+diagVirtualTextWrap |String| option. Wrapping of diagnostics messages
+ if |lsp-opt-showDiagWithVirtualText| is set to true.
+ Allowed values are 'default', 'wrap' or 'truncate'
+ By default this is set to 'default',
+
+ *lsp-opt-echoSignature*
+echoSignature |Boolean| option. In insert mode, echo the current
+ symbol signature instead of showing it in a popup.
+ By default this is set to false.
+
+ *lsp-opt-hideDisabledCodeActions*
+hideDisabledCodeActions |Boolean| option. Hide all the disabled code actions.
+ By default this is set to false.
+
+ *lsp-opt-highlightDiagInline*
+highlightDiagInline |Boolean| option. Highlight the diagnostics inline.
+ By default this is set to true.
+
+ *lsp-opt-hoverInPreview*
+hoverInPreview |Boolean| option. Show |:LspHover| in a preview window
+ instead of a popup.
+ By default this is set to false.
+
+ *lsp-opt-ignoreMissingServer*
+ignoreMissingServer |Boolean| option. Do not print a missing language
+ server executable. By default this is set to false.
+
+ *lsp-opt-keepFocusInDiags*
+keepFocusInDiags |Boolean| option. Focus on the location list window
+ after ":LspDiag show".
+ By default this is set to true.
+
+ *lsp-opt-keepFocusInReferences*
+keepFocusInReferences |Boolean| option. Focus on the location list window
+ after LspShowReferences.
+ By default this is set to true.
+
+ *lsp-opt-noNewlineInCompletion*
+noNewlineInCompletion |Boolean| option. Suppress adding a new line on
+ completion selection with <CR>.
+ By default this is set to false.
+
+ *lsp-opt-omniComplete*
+omniComplete |Boolean| option. Enables or disables omni-completion.
+ By default this is set to v:false. If "autoComplete"
+ is set to v:false, then omni-completion is enabled by
+ default. By setting "omniComplete" option to v:false,
+ omni-completion can also be disabled.
+
+ *lsp-opt-outlineOnRight*
+outlineOnRight |Boolean| option. Open the outline window on the
+ right side, by default this is false.
+
+ *lsp-opt-outlineWinSize*
+outlineWinSize |Number| option. The size of the symbol Outline
+ window. By default this is set to 20.
+
+ *lsp-opt-semanticHighlight*
+semanticHighlight |Boolean| option. Enables or disables semantic
+ highlighting.
+ By default this is set to false.
+
+ *lsp-opt-showDiagInBalloon*
+showDiagInBalloon |Boolean| option. When the mouse is over a range of
+ text referenced by a diagnostic, display the
+ diagnostic text in a balloon. By default this is set
+ to true. In a GUI Vim, this needs the |+balloon_eval|
+ feature. In a terminal Vim, this needs the
+ |+balloon_eval_term| feature. In a terminal Vim,
+ 'mouse' option should be set to enable mouse.
+ If this option is set to true, then the 'ballooneval'
+ and 'balloonevalterm' options are set.
+
+ *lsp-opt-showDiagInPopup*
+showDiagInPopup |Boolean| option. When using the ":LspDiag current"
+ command to display the diagnostic message for the
+ current line, use a popup window to display the
+ message instead of echoing in the status area.
+ By default this is set to true.
+
+ *lsp-opt-showDiagOnStatusLine*
+showDiagOnStatusLine |Boolean| option. Show a diagnostic message on a
+ status line. By default this is set to false.
+
+ *lsp-opt-showDiagWithSign*
+showDiagWithSign |Boolean| option. Place a sign on lines with
+ diagnostics. By default this is set to true. The
+ "autoHighlightDiags" option should be set to true.
+
+ *lsp-opt-showDiagWithVirtualText*
+showDiagWithVirtualText |Boolean| option. Show diagnostic message text from
+ the language server with virtual text. By default
+ this is set to false. The "autoHighlightDiags" option
+ should be set to true.
+ Needs Vim version 9.0.1157 or later.
+
+ *lsp-opt-showInlayHints*
+showInlayHints |Boolean| option. Show inlay hints from the language
+ server. By default this is set to false. The inlay
+ hint text is displayed as a virtual text. Needs Vim
+ version 9.0.0178 or later.
+
+ *lsp-opt-showSignature*
+showSignature |Boolean| option. In insert mode, automatically show
+ the current symbol signature in a popup.
+ By default this is set to true.
+
+ *lsp-opt-snippetSupport*
+snippetSupport |Boolean| option. Enable snippet completion support.
+ Need a snippet completion plugin like vim-vsnip.
+ By default this is set to false.
+
+ *lsp-opt-ultisnipsSupport*
+ultisnipsSupport |Boolean| option. Enable SirVer/ultisnips support.
+ Need a snippet completion plugin SirVer/ultisnips.
+ By default this is set to false.
+
+ *lsp-opt-vssnipSupport*
+vsnipSupport |Boolean| option. Enable hrsh7th/vim-vsnip support.
+ Need snippet completion plugins hrsh7th/vim-vsnip
+ and hrsh7th/vim-vsnip-integ. Make sure
+ ultisnipsSupport is set to false before enabling this.
+ By default this option is set to false.
+
+ *lsp-opt-usePopupInCodeAction*
+usePopupInCodeAction |Boolean| option. When using the |:LspCodeAction|
+ command to display the code action for the current
+ line, use a popup menu instead of echoing.
+ By default this is set to false.
+
+ *lsp-opt-useQuickfixForLocations*
+useQuickfixForLocations |Boolean| option. Show |:LspShowReferences| in a
+ quickfix list instead of a location list.
+ By default this is set to false.
+
+ *lsp-opt-useBufferCompletion*
+useBufferCompletion |Boolean| option. If enabled, the words from the
+ current buffer are added to the auto completion list.
+ By default this is set to false.
+
+ *lsp-opt-bufferCompletionTimeout*
+bufferCompletionTimeout |Number| option. Specifies how long (in milliseconds)
+ to wait while processing current buffer for
+ autocompletion words. If set too high Vim performance
+ may degrade as the current buffer contents are
+ processed every time the completion menu is displayed.
+ If set to 0 the entire buffer is processed without
+ regard to timeout.
+ By default this is set to 100 ms.
+
+ *lsp-opt-filterCompletionDuplicates*
+filterCompletionDuplicates |Boolean| option. If enabled, duplicate completion
+ items sent from the server will be filtered to only
+ include one instance of the duplicates.
+
+For example, to disable the automatic placement of signs for the LSP
+diagnostic messages, you can add the following line to your .vimrc file: >
+
+ call LspOptionsSet({'autoHighlightDiags': false})
+<
+ *LspOptionsGet()*
+The LspOptionsGet() function returns a |Dict| of all the LSP plugin options,
+To get a particular option value you can use the following: >
+
+ echo LspOptionsGet()['autoHighlightDiags']
+<
+==============================================================================
+5. Commands *lsp-commands*
+
+A description of the various commands provided by this plugin is below. You
+can map these commands to keys and make it easier to invoke them.
+
+ *:LspCodeAction*
+:LspCodeAction [query] Apply the code action supplied by the language server
+ to the diagnostic in the current line. This works only
+ if there is a diagnostic message for the current line.
+ You can use the ":LspDiag current" command to display
+ the diagnostic for the current line.
+
+ When [query] is given the code action starting with
+ [query] will be applied. [query] can be a regexp
+ pattern, or a digit corresponding to the index of the
+ code actions in the created prompt.
+
+ When [query] is not given you will be prompted to
+ select one of the actions supplied by the language
+ server.
+
+ *:LspCodeLens*
+:LspCodeLens Display a list of code lens commands available for the
+ current buffer and apply the selected code lens
+ command.
+
+ *:LspDiag-current*
+:LspDiag current Displays the diagnostic message (if any) for the
+ current line. If the option 'showDiagInPopup' is set
+ to true (default), then the message is displayed in
+ a popup window. Otherwise the message is displayed in
+ the status message area.
+
+:LspDiag! current Only display a diagnostic message if it's directly
+ under the cursor. Otherwise works exactly like
+ ":LspDiag current"
+
+ To show the current diagnotic under the cursor while
+ moving around the following autocmd can be used: >
+
+ augroup LspCustom
+ au!
+ au CursorMoved * silent! LspDiag! current
+ augroup END
+<
+ *:LspDiag-first*
+:LspDiag first Jumps to the location of the first diagnostic message
+ for the current file.
+
+ *:LspDiag-here*
+:LspDiag here Jumps to the location of the diagnostic message in
+ the current line (start from current column).
+
+:LspDiag highlight disable *:LspDiag-highlight-disable*
+ Disable highlighting lines with a diagnostic message
+ for the current Vim session.
+ To always disable the highlighting, set the
+ autoHighlightDiags option to false.
+
+:LspDiag highlight enable *:LspDiag-highlight-enable*
+ Enable highlighting lines with a diagnostic message
+ for the current Vim session. Note that highlighting
+ lines with a diagnostic message is enabled by default.
+
+ *:LspDiag-last*
+:LspDiag last Jumps to the location of the first diagnostic message
+ for the current file.
+
+ *:LspDiag-next*
+:[count]LspDiag next Go to the [count] diagnostic message after the current
+ cursor position. If [count] is omitted, then 1 is
+ used. If [count] exceeds the number of diagnostics
+ after the current position, then the last diagnostic
+ is selected.
+
+ *:LspDiag-prev*
+:[count]LspDiag prev Go to the [count] diagnostic message before the
+ current cursor position. If [count] is omitted, then
+ 1 is used. If [count] exceeds the number of
+ diagnostics before the current position, then first
+ last diagnostic is selected.
+
+ *:LspDiag-show*
+:LspDiag show Creates a new location list with the diagnostics
+ messages (if any) from the language server for the
+ current file and opens the location list window. You
+ can use the Vim location list commands to browse the
+ list.
+
+ *:LspDocumentSymbol*
+:LspDocumentSymbol Display the symbols in the current file in a popup
+ menu. When a symbol is selected in the popup menu by
+ pressing <Enter> or <Space>, jump to the location of
+ the symbol.
+
+ The <Up>, <Down>, <Tab>, <S-Tab>, <C-N>, <C-P>,
+ <ScrollWheelUp>, ScrollWheelDown> keys can be used to
+ scroll popup menu one item at a time. <PageUp> and
+ <PageDown> can be used to scroll a page of popup
+ window, while <C-F> and <C-B> can be used to scroll a
+ page of underlying window. The <Esc> or <Ctrl-C> keys
+ can be used to cancel the popup menu.
+
+ If one or more keyword characters are typed, then only
+ the symbols containing the keyword characters are
+ displayed in the popup menu. Fuzzy searching is used
+ to get the list of matching symbols. The <BS> key can
+ be used to erase the last typed character. The <C-U>
+ key can be used to erase all the characters.
+
+ When scrolling through the symbols in the popup menu,
+ the corresponding range of lines is highlighted.
+
+ *:LspFold*
+:LspFold Create folds for the current buffer.
+
+ *:LspFormat*
+:LspFormat Format the current file using the language server. The
+ 'shiftwidth' and 'expandtab' values set for the
+ current buffer are used when format is applied.
+
+:{range}LspFormat Format the specified range of lines in the current
+ file using the language server.
+
+ *:LspGotoDeclaration*
+:[count]LspGotoDeclaration
+ Jumps to the declaration of the symbol under the
+ cursor. The behavior of this command is similar to the
+ |:LspGotoDefinition| command.
+
+ *:LspGotoDefinition*
+:[count]LspGotoDefinition
+ Jumps to the [count] definition of the symbol under
+ the cursor. If there are multiple matches and [count]
+ isn't specified, then a location list will be created
+ with the list of locations.
+
+ If there is only one location, or [count] is provided
+ then the following will apply:
+
+ If the file is already present in a window, then jumps
+ to that window. Otherwise, opens the file in a new
+ window. If the current buffer is modified and
+ 'hidden' is not set or if the current buffer is a
+ special buffer, then a new window is opened. If the
+ jump is successful, then the current cursor location
+ is pushed onto the tag stack. The |CTRL-T| command
+ can be used to go back up the tag stack. Also the
+ |``| mark is set to the position before the jump.
+
+ This command supports |:command-modifiers|. You can
+ use the modifiers to specify whether a new window or
+ a new tab page is used and where the window is opened.
+ Example(s): >
+
+ # Open a horizontally split window
+ :topleft LspGotoDefinition
+ # Open a vertically split window
+ :vert LspGotoDefinition
+ # Open a new tab page
+ :tab LspGotoDefinition
+<
+ You may want to map a key to invoke this command: >
+
+ nnoremap <buffer> gd <Cmd>LspGotoDefinition<CR>
+ nnoremap <buffer> <C-W>gd <Cmd>topleft LspGotoDefinition<CR>
+<
+ Or if you want to support [count]gd >
+
+ nnoremap <buffer> gd <Cmd>execute v:count .. 'LspGotoDefinition'<CR>
+ nnoremap <buffer> <C-W>gd <Cmd>execute 'topleft ' .. v:count .. 'LspGotoDefinition'<CR>
+<
+ *:LspGotoImpl*
+:[count]LspGotoImpl Jumps to the implementation of the symbol under the
+ cursor. The behavior of this command is similar to the
+ |:LspGotoDefinition| command. Note that not all the
+ language servers support this feature.
+
+ You may want to map a key to invoke this command: >
+
+ nnoremap <buffer> gi <Cmd>LspGotoImpl<CR>
+<
+ *:LspGotoTypeDef*
+:[count]LspGotoTypeDef Jumps to the type definition of the symbol under the
+ cursor. The behavior of this command is similar to the
+ |:LspGotoDefinition| command. Note that not all the
+ language servers support this feature.
+
+ You may want to map a key to invoke this command: >
+
+ nnoremap <buffer> gt <Cmd>LspGotoTypeDef<CR>
+<
+ *:LspHighlight*
+:LspHighlight Highlights all the matches for the symbol under
+ cursor. The text, read and write references to the
+ symbol are highlighted using Search, DiffChange and
+ DiffDelete highlight groups respectively.
+
+ *:LspHighlightClear*
+:LspHighlightClear Clears all the symbol matches highlighted by the
+ |:LspHighlight| command.
+
+ *:LspHover*
+:LspHover Show the documentation for the symbol under the cursor
+ in a popup window. The following keys can be used to
+ scroll the popup window:
+
+ <CTRL-E> - Scroll window downwards by a line.
+ <CTRL-D> - Scroll window downwards by 'scroll'
+ lines.
+ <CTRL-F> - Scroll window downards by a page.
+ <PageDown> - ditto.
+ <CTRL-Y> - Scroll window upwards by a line.
+ <CTRL-U> - Scroll window upwards by 'scroll'
+ lines.
+ <CTRL-B> - Scroll window upwards by a page.
+ <PageUp> - ditto.
+ <CTRL-Home> - Goto the first line
+ <CTRL-End> - Goto the last line
+
+ Pressing any other key will close the popup window.
+
+ If you want to show the symbol documentation in the
+ |preview-window| instead of in a popup window set >
+
+ LspOptionsSet({'hoverInPreview': true})
+<
+ You can use the |:pclose| command to close the preview
+ window.
+
+ You can use the |K| key in normal mode to display the
+ documentation for the keyword under the cursor by
+ setting the 'keywordprg' Vim option: >
+
+ :set keywordprg=:LspHover
+<
+ *:LspIncomingCalls*
+:LspIncomingCalls Display a hierarchy of symbols calling the symbol
+ under the cursor in a window. See
+ |lsp-call-hierarchy| for more information. Note that
+ not all the language servers support this feature.
+
+ *:LspInlayHints*
+:LspInlayHints Enable or disable inlay hints. Supports the "enable"
+ and "disable" arguments. When "enable" is specified,
+ enables the inlay hints for all the buffers with a
+ language server that supports inlay hints. When
+ "disable" is specified, disables the inlay hints.
+
+ *:LspOutoingCalls*
+:LspOutgoingCalls Display a hierarchy of symbols called by the symbol
+ under the cursor in a window. See
+ |lsp-call-hierarchy| for more information. Note that
+ not all the language servers support this feature.
+
+ *:LspOutline*
+:[count]LspOutline Opens a vertically split window with the list of
+ symbols defined in the current file. The current
+ symbol is highlighted. The symbols are grouped by
+ their type. You can select a symbol and press <Enter>
+ to jump to the position of the symbol. As you move the
+ cursor in a file, the current symbol is automatically
+ highlighted in the outline window. If you open a new
+ file, the outline window is automatically updated with
+ the symbols in the new file. Folds are created in the
+ outline window for the various group of symbols.
+
+ You can use |lsp-opt-outlineOnRight| and
+ |lsp-opt-outlineWinSize| to customize the placement
+ and size of the window.
+
+ This command also supports |:command-modifiers|. You
+ can use the modifiers specify the position of the
+ window. Note that the default is ":vert :topleft" or
+ ":vert :botright" depending on
+ |lsp-opt-outlineOnRight|
+
+ This command also supports providing a [count] to
+ specify the size of the window. Note that this
+ overrides the values defined in
+ |lsp-opt-outlineWinSize|.
+ Example: >
+
+ # Open the outline window just above the current
+ # window
+ :aboveleft LspOutline
+
+ # Open the outline window just next to the current
+ # window, this is different from the default, when
+ # you have multiple splits already
+ :vert aboveleft LspOutline
+
+ # Same as above, but with a width of 50
+ :vert aboveleft 50LspOutline
+<
+ *:LspPeekDeclaration*
+:[count]LspPeekDeclaration
+ Displays the line where the symbol under the
+ cursor is declared in a popup window. The
+ behavior of this command is similar to the
+ |:LspPeekDefinition| command.
+
+ *:LspPeekDefinition*
+:[count]LspPeekDefinition
+ Displays the line where the symbol under the cursor is
+ defined in a popup window. The symbol is highlighted
+ in the popup window. Moving the cursor or pressing
+ <Esc> will close the popup window.
+ When more than one symbol is found all of them will be
+ shown. The corresponding file for the symbol is
+ displayed in another popup window. As the selection
+ in the symbol popup menu changes, the file in the
+ popup is updated.
+ When [count] is provided only the [count] symbol will
+ be shown.
+
+ *:LspPeekImpl*
+:[count]LspPeekImpl Displays the implementation of the symbol under the
+ cursor in a popup window. The behavior of this
+ command is similar to the |:LspPeekDefinition|
+ command. Note that not all the language servers
+ support this feature.
+
+ *:LspPeekReferences*
+:LspPeekReferences Displays the list of references to the symbol under
+ cursor in a popup menu. The corresponding file for
+ the reference is displayed in another popup window.
+ As the selection in the reference popup menu changes,
+ the file in the popup is updated.
+
+ *:LspPeekTypeDef*
+:[count]LspPeekTypeDef Displays the line where the type of the symbol under
+ the cursor is defined in a popup window. The
+ behavior of this command is similar to the
+ |:LspPeekDefinition| command. Note that not all the
+ language servers support this feature.
+
+ *:LspRename*
+:LspRename [newName] Rename the current symbol.
+
+ When [newName] is not given, then you will be prompted
+ to enter the new name for the symbol. You can press
+ <Esc> or enter an empty string in the prompt to cancel
+ the operation.
+
+ *:LspSelectionExpand*
+:LspSelectionExpand Visually select the region of the symbol under the
+ cursor. In visual mode, expands the current symbol
+ visual region selection to include the next level.
+
+ For example, if the cursor is on a "for" statement,
+ this command selects the "for" statement and the body
+ of the "for" statement.
+
+ It is useful to create a visual map to use this
+ command. Example: >
+
+ xnoremap <silent> <Leader>e <Cmd>LspSelectionExpand<CR>
+<
+ With the above map, you can press "\e" in visual mode
+ successively to expand the current symbol visual
+ region.
+
+ *:LspSelectionShrink*
+:LspSelectionShrink Shrink the current symbol range visual selection. It
+ is useful to create a visual map to use this command.
+ Example: >
+
+ xnoremap <silent> <Leader>s <Cmd>LspSelectionShrink<CR>
+<
+ With the above map, you can press "\s" in visual mode
+ successively to shrink the current symbol visual
+ region.
+
+ *:LspServer*
+:LspServer { debug | restart | show | trace }
+ Command to display and control the language server for
+ the current buffer. Each argument has additional
+ sub-commands which are described below.
+
+ debug { on | off | messages | errors }
+ Command to enable or disable the language server
+ debug messages and to display the debug messages
+ and error messages received from the language
+ server. The following sub-commands are supported:
+ errors Open the log file containing the
+ language server error messages.
+ messages
+ Open the log file containing the
+ language server debug messages.
+ off Disable the logging of the language
+ server messages.
+ on Enable the logging of the messages
+ emitted by the language server in the
+ standard output and standard error.
+ By default, the language server messages are not
+ logged. On a Unix-like system, when enabled,
+ these messages are logged to the
+ /tmp/lsp-<server-name>.log and
+ /tmp/lsp-<server-name>.err file respectively. On
+ MS-Windows, the %TEMP%/lsp-<server-name>.log and
+ %TEMP%/lsp-<server-name>.err% files are used. See
+ |lsp-debug| for more information.
+
+ restart
+ Restart (stop and then start) the language server
+ for the current buffer. All the loaded buffers
+ with the same filetype as the current buffer are
+ added back to the server.
+
+ show {capabilities | initializeRequest | messages
+ | status}
+ The following sub-commands are supported:
+ capabilities
+ Display the list of language server
+ capabilities for the current buffer.
+ The server capabilities are described
+ in the LSP protocol specification
+ under the "ServerCapabilities"
+ interface.
+ initializeRequest
+ Display the contents of the language
+ server initialization request message
+ (initialize).
+ messages
+ Display the log messages received from
+ the language server. This includes
+ the messages received using the
+ "window/logMessage" and "$/logTrace"
+ LSP notifications.
+ status
+ Display the language server status for
+ the current buffer. The output shows
+ the path to the language server
+ executable and the server status.
+
+ trace { off | messages | verbose }
+ Set the language server debug trace value using
+ the "$/setTrace" command.
+
+ *:LspShowAllServers*
+:LspShowAllServers Displays the list of registered language servers and
+ their status. The language servers are registered
+ using the LspAddServer() function. The output is
+ displayed in a scratch buffer. The output shows the
+ Vim file type, the corresponding language server
+ status and the path to the language server executable.
+ The language server information for each buffer is
+ also shown.
+
+ *:LspShowReferences*
+:LspShowReferences Creates a new location list with the list of locations
+ where the symbol under the cursor is referenced and
+ opens the location window. If you want to show the
+ references in a quickfix list instead of in a location
+ list set >
+
+ LspOptionsSet({'useQuickfixForLocations': true})
+<
+ *:LspShowSignature*
+:LspShowSignature Displays the signature of the symbol (e.g. a function
+ or method) before the cursor in a popup.
+
+ The popup is also automatically displayed in insert
+ mode after entering a symbol name followed by a
+ separator (e.g. a opening parenthesis). To disable
+ this, you can set the showSignature option to false in
+ your .vimrc file: >
+
+ LspOptionsSet({'showSignature': false})
+<
+ Default is true.
+
+ You can get the function signature echoed in cmdline
+ rather than displayed in popup if you use >
+
+ LspOptionsSet({'echoSignature': true})
+<
+ Default is false.
+
+ *:LspSubTypeHierarchy*
+:LspSubTypeHierarchy Show the sub type hierarchy for the symbol under the
+ cursor in a popup window. The file containing the
+ type is shown in another popup window. You can jump
+ to the location where a type is defined by browsing
+ the popup menu and selecting an entry.
+
+ *:LspSuperTypeHierarchy*
+:LspSuperTypeHierarchy Show the super type hierarchy for the symbol under the
+ cursor in a popup window. The file containing the
+ type is shown in another popup window. As the current
+ entry in the type hierarchy popup menu changes, the
+ file popup window is updated to show the location
+ where the type is defined. You can jump to the
+ location where a type is defined by selecting the
+ entry in the popup menu.
+
+ Note that the type hierarchy support is based on the
+ protocol supported by clangd. This is different from
+ the one specified in the 3.17 of the LSP standard.
+
+ *:LspSwitchSourceHeader*
+:LspSwitchSourceHeader Switch between source and header files. This is a
+ Clangd specific extension and only works with C/C++
+ source files.
+
+ *:LspSymbolSearch*
+:LspSymbolSearch <sym> Perform a workspace wide search for the symbol <sym>.
+ If <sym> is not supplied, then you will be prompted to
+ enter the symbol name (the keyword under the cursor is
+ used as the default). If there is only one matching
+ symbol, then the cursor will be positioned at the
+ symbol location. Otherwise a popup window is opened
+ with the list of matching symbols. You can enter a
+ few characters to narrow down the list of matches. The
+ displayed symbol name can be erased by pressing
+ <Backspace> or <C-U> and a new symbol search pattern
+ can be entered. You can close the popup menu by
+ pressing the escape key or by pressing CTRL-C.
+
+ In the popup menu, the following keys can be used:
+
+ CTRL-F - Scroll one page forward
+ <PageDown> - idem
+ CTRL-B - Scroll one page backward
+ <PageUp> - idem
+ CTRL-Home - Jump to the first entry
+ CTRL-End - Jump to the last entry
+ <Up> - Go up one entry
+ <C-P> - idem
+ <Down> - Go down one entry
+ <C-N> - idem
+ <Enter> - Open the selected file
+ <Esc> - Close the popup menu
+ <CTRL-C> - idem
+ <BS> - Erase one character from the
+ filter text
+ <C-H> - idem
+ <C-U> - Erase the filter text
+
+ Any other alphanumeric key will be used to narrow down
+ the list of names displayed in the popup menu. When
+ you type a filter string, then only the symbols fuzzy
+ matching the string are displayed in the popup menu.
+ You can enter a new search pattern to do a workspace
+ wide symbol search.
+
+ This command accepts |:command-modifiers| which can be
+ used to jump to a symbol in a horizontally or
+ vertically split window or a new tab page: >
+
+ :topleft LspSymbolSearch foo
+ :vert LspSymbolSearch bar
+ :tab LspSymbolSearch baz
+<
+ *:LspWorkspaceAddFolder*
+:LspWorkspaceAddFolder {folder}
+ Add a folder to the workspace
+
+:LspWorkspaceListFolders *:LspWorkspaceListFolders*
+ Show the list of folders in the workspace.
+
+ *:LspWorkspaceRemoveFolder*
+:LspWorkspaceRemoveFolder {folder}
+ Remove a folder from the workspace
+
+==============================================================================
+6. Insert Mode Completion *lsp-ins-mode-completion*
+
+By default, when you are in insert mode, the LSP plugin will automatically
+display suggestions for the symbol under the cursor in an insert-completion
+popup menu. The keys specified in |popupmenu-keys| can be used to interact
+with this menu.
+
+To disable this auto-completion feature for all files, you can set the
+"autoComplete" option to false in your .vimrc file using the |LspOptionsSet()|
+function: >
+
+ call LspOptionsSet({'autoComplete': false})
+<
+By setting the "autoComplete" option to |v:false|, the LSP plugin will no
+longer automatically trigger completion suggestions in insert mode. Instead,
+it will use omni-completion (|compl-omni|) and set the 'omnifunc' option for
+buffers that have a registered language server. To manually trigger symbol
+completion in insert mode, you can press CTRL-X CTRL-O. This key combination
+will invoke completion using the suggestions provided by the language server.
+
+To enable omni-completion for all the buffers, set the "omniComplete" option
+to v:true. To explicitly disable omni-completion for all the buffers, set the
+"omniComplete" option to v:false (default).
+
+In addition to the general auto-completion behavior discussed above, you
+have the option to enable or disable omni-completion for a specific language
+server when registering it for a particular filetype.
+
+To do this, you can set the 'omnicompl' item to |v:false| in the configuration
+when registering the language server for the desired filetype. If the
+'omnicompl' item is not specified, omni-completion is enabled by default.
+
+Here's an example of how to disable omni-completion for Python: >
+
+ vim9script
+ var lspServers = [
+ {
+ filetype: 'python',
+ omnicompl: false,
+ path: '/usr/local/bin/pyls',
+ args: ['--check-parent-process', '-v']
+ }
+ ]
+<
+In this example, the language server for Python is registered using the
+|LspAddServer()| function, and the 'omnicompl' item is explicitly set to
+|v:false|. As a result, omni-completion will be disabled for Python files
+associated with this language server.
+
+Please note that if 'omnicompl' is not included in the configuration
+when registering the language server, omni-completion will be enabled by
+default.
+
+In insert-mode completion, the plugin sends a completion request message to
+the language server and obtains a list of potential completion matches based
+on the current cursor position. To achieve this, the plugin retrieves the
+keyword immediately preceding the cursor (refer to 'iskeyword' setting) and
+then filters the list of completion items received from the language server
+based on this keyword. The resulting filtered list is displayed as the
+completion menu.
+
+It's worth noting that different language servers handle completion filtering
+in distinct ways. Some servers perform the filtering directly on the
+server-side, while others delegate this task to the client-side, which is the
+plugin in this context.
+
+By default, the plugin uses a case-sensitive comparison method to filter the
+returned completion items. However, you have the flexibility to customize this
+behavior by modifying the "completionMatcher" option. This option allows you
+to switch between case-insensitive or fuzzy comparison methods as per your
+preference and requirements for completion matching.
+
+In addition to automatic completion and omni completion, there is a
+possibility to utilize external completion engines with the LSP client. This
+can be achieved by repurposing the |g:LspOmniFunc| function. The external
+completion engine adapter needs to invoke this function twice, following the
+approach outlined in the |complete-functions| documentation.
+
+The process works as follows:
+
+1. First Invocation: The external completion engine adapter calls
+ |g:LspOmniFunc| to initiate a request to the LSP server for completion
+ candidates.
+2. After the first invocation, a request is sent to the LSP server to find
+ completion candidates.
+3. Second Invocation: The external completion engine adapter calls
+ |g:LspOmniFunc| again to retrieve the matches returned by the LSP server.
+4. If the LSP server is not ready to reply immediately, |g:LspOmniFunc| waits
+ for up to 2 seconds.
+5. However, this wait could block the caller from performing other tasks,
+ which might be a concern for asynchronous completion engines.
+6. To address this issue, the adapter can use the |g:LspOmniCompletePending|
+ function, which allows for a non-blocking check. It returns true
+ immediately if the language server is not ready to respond yet.
+7. To proceed with the second invocation of g:LspOmniFunc, it is crucial to
+ ensure that |g:LspOmniCompletePending| returns false, indicating that the
+ language server is now ready to provide the completion matches.
+
+==============================================================================
+7. Diagnostics *lsp-diagnostics*
+
+The LSP plugin offers a feature to highlight syntax errors, warnings, and
+static analysis warnings in a source file by placing signs in the sign column.
+These signs serve as visual indicators of the diagnostics reported by the
+language server.
+
+To interact with these diagnostics, you can use various commands provided by
+the LSP plugin:
+
+1. ":LspDiag show": This command displays all the diagnostic messages for the
+ current file in a location-list window. The location-list window allows
+ you to view a list of all the diagnostic messages, along with their
+ corresponding line numbers and descriptions.
+2. ":LspDiag first": Use this command to jump directly to the line containing
+ the first diagnostic message. It helps you quickly navigate to the
+ location of the initial issue detected by the language server.
+3. ":LspDiag next": With this command, you can navigate to the next nearest
+ line with a diagnostic message. It helps you step through the list of
+ diagnostics one by one.
+4. ":LspDiag prev": Conversely, this command allows you to jump to the
+ previous nearest line with a diagnostic message. It is useful for
+ reviewing diagnostics in reverse order.
+5. ":LspDiag here": If you want to focus solely on the diagnostic message for
+ the current line, you can use this command to jump directly to it.
+6. ":LspDiag current": This command displays the entire diagnostic message
+ from the language server for the current line. It provides detailed
+ information about the specific issue and its description.
+
+By using these commands, you can efficiently navigate and inspect the
+diagnostics reported by the language server, making it easier to identify and
+address syntax errors, warnings, or static analysis issues in your code.
+
+By default, the LSP plugin marks lines with diagnostic messages by placing a
+sign on them and highlighting the range of text associated with the
+diagnostic. However, you have the option to customize this behavior by
+adjusting certain configuration settings:
+
+1. Disabling Automatic Sign Placement: If you wish to prevent the automatic
+ placement of signs on lines with diagnostic messages, you can achieve this
+ by setting the "showDiagWithSign" option to |v:false|. By default, this
+ option is set to |v:true|, meaning that signs are automatically placed on
+ lines with diagnostics.
+2. Disabling Diagnostic Text Highlighting: If you prefer not to have the
+ diagnostic text highlighted, you can do so by setting the
+ "highlightDiagInline" option to |v:false|. By default, this option is set
+ to |v:true|, resulting in the highlighting of the text range associated
+ with each diagnostic.
+3. Highlight Group for Line with Diagnostics: The LSP plugin uses the
+ "LspDiagLine" highlight group to highlight lines containing diagnostics.
+ By default, this highlight group is not set, allowing you to define your
+ own highlighting style for lines with diagnostics if desired.
+
+In addition to the default display of the diagnostic messages with signs and
+text highlighting, the LSP plugin offers the option to present the diagnostic
+message as virtual text, located near the relevant location of the
+diagnostics. To enable this feature, you can set the
+"showDiagWithVirtualText" option to |v:true|. However, please note that this
+functionality requires Vim version 9.0.1157 or later. By default, this option
+is set to |v:false|, meaning that virtual text display is not activated.
+
+The position of the virtual text can be controlled using the
+"diagVirtualTextAlign" option, which determines its alignment relative to the
+affected line. By default, this option is set to 'above', which places the
+virtual text above the line with the diagnostic message. The other supported
+values for "diagVirtualTextAlign" are 'below', which positions the virtual
+text below the affected line, and 'after', which displays the virtual text
+immediately after the text on the affected line.
+
+The wrapping of the virtual text can be controlled using the
+"diagVirtualTextWrap" option. By default, this option is set to 'default',
+which will 'truncate' virtual text placed 'above' or 'below' the affected
+line, and 'wrap' text placed 'after' the affected line. Setting the value to
+'wrap' or 'truncate' will force the specified behavior for the current
+value of "diagVirtualTextAlign". If 'truncate' is used while
+"diagVirtualTextAlign" is set to 'after', and a diagnostic message has already
+been truncated for the affected line, then further diagnostics will be placed
+below the affected line.
+
+The LSP plugin offers convenient ways to highlight diagnostic messages, making
+it easier to spot errors, warnings, hints, or informational notices within
+your code. By default, the plugin automatically highlights the range of text
+associated with each diagnostic message when the "highlightDiagInline" option
+is set to |v:true.|
+
+The highlighting is done using different highlight groups based on the type of
+diagnostic message:
+
+ "LspDiagInlineError" for error messages.
+ "LspDiagInlineHint" for hints.
+ "LspDiagInlineInfo" for informational messages.
+ "LspDiagInlineWarning" for warning messages.
+
+If you wish to temporarily disable the automatic diagnostic highlighting for
+the current Vim session, you can achieve this using the ":LspDiag highlight
+disable" command. When you want to re-enable the highlighting, you can use
+the ":LspDiag highlight enable" command.
+
+To permanently disable the automatic highlighting of diagnostics, you can set
+the "autoHighlightDiags" option to |v:false| in your .vimrc file. This
+configuration can be achieved using the |LspOptionsSet()| function: >
+
+ call LspOptionsSet({'autoHighlightDiags': v:false})
+<
+By default, the "autoHighlightDiags" option is set to |v:true|, ensuring that
+diagnostic messages are automatically highlighted during your coding sessions.
+
+The lsp#lsp#ErrorCount() function returns the count of diagnostic messages in
+the current buffer, categorized by their types. When called, this function
+returns a Dictionary containing four keys: "Info," "Hint," "Warn," and
+"Error." Each key corresponds to a specific diagnostic type, and its
+associated value is the number of diagnostic messages of that particular type
+found in the buffer. With the information gathered using this function, you
+can easily display the number of diagnostics in the current buffer in your
+'statusline'.
+
+For some diagnostic errors/warnings, the language server may provide an
+automatic fix. To apply this fix, you can use the |:LspCodeAction| command.
+This command applies the action provided by the language server (if any) for
+the current line.
+
+The ":LspDiag show" command creates a new location list with the current list
+of diagnostics for the current buffer. To automatically refresh the location
+list with the latest diagnostics received from the language server, you can
+set the "autoPopulateDiags" option to |v:true|. By default this option is set
+to |v:false|. When new diagnostics are received for a buffer, if a location
+list with the diagnostics is already present, then it is refreshed with the
+new diagnostics.
+
+In GUI Vim or terminal Vim with the 'balloonevalterm' option enabled, a
+helpful feature allows you to view diagnostic messages in a popup balloon when
+you hover the mouse over the affected range of text. This provides a
+convenient way to quickly access diagnostic information without the need to
+execute additional commands or navigate through the location list.
+
+By default, the LSP plugin is configured to display diagnostic messages in the
+popup balloon, enhancing the user experience and providing visual feedback as
+you interact with your code. This default behavior is governed by the
+"showDiagInBalloon" option, which is set to |v:true| by default.
+
+However, if you prefer not to see the diagnostic messages in the popup
+balloons and prefer to rely solely on other methods, you have the flexibility
+to customize this behavior. By setting the "showDiagInBalloon" option to
+|v:false|, you can disable the display of diagnostic messages in the popup
+balloons. This can be useful if you find the balloons intrusive or if you
+prefer to view diagnostics through other means, such as the location list or
+the status line.
+
+To display the diagnostic message for the current line in the status area, you
+can set the "showDiagOnStatusLine" option to |v:true|. By default, this
+option is set to |v:false|.
+
+By default, the ":LspDiag current" command displays the diagnostic message for
+the current line in a popup window. To display the message in the status
+message area instead, you can set the 'showDiagInPopup' option to |v:false|.
+By default this is set to |v:true|.
+
+The lsp#diag#GetDiagsForBuf() function can be used to get all the LSP
+diagnostics in a buffer. This function optionally accepts a buffer number.
+If the buffer number argument is not specified, then the current buffer is
+used. This function returns a |List| of diagnostics sorted by their line and
+column number. Each diagnostic is a |Dict| returned by the language server.
+
+==============================================================================
+8. Tag Function *lsp-tagfunc*
+
+The |:LspGotoDefinition| command can be used jump to the location where a
+symbol is defined. To jump to the symbol definition using the Vim
+|tag-commands|, you can set the 'tagfunc' option to the 'lsp#lsp#TagFunc'
+function: >
+
+ setlocal tagfunc=lsp#lsp#TagFunc
+<
+After setting the above option, you can use |Ctrl-]| and other tag related
+commands to jump to the symbol definition.
+
+Note that most of the language servers return only one symbol location even if
+the symbol is defined in multiple places in the code.
+
+==============================================================================
+9. Code Formatting *lsp-format*
+
+The |:LspFormat| command can be used to format either the entire file or a
+selected range of lines using the language server. The 'shiftwidth' and
+'expandtab' values set for the current buffer are used when format is applied.
+
+To format code using the 'gq' command, you can set the 'formatexpr' option: >
+
+ setlocal formatexpr=lsp#lsp#FormatExpr()
+<
+==============================================================================
+10. Call Hierarchy *lsp-call-hierarchy*
+
+The |:LspIncomingCalls| and the |:LspOutoingCalls| commands can be used to
+display the call hierarchy of a symbol. For example, the functions calling a
+function or the functions called by a function. These two commands open a
+window containing the call hierarchy tree. You can use the Vim motion
+commands to browse the call hierarchy.
+
+In the call hierarchy tree window, the following keys are supported:
+
+<Enter> Jump to the location of the symbol under the
+ cursor.
+- Expand and show the symbols calling or called
+ by the symbol under the cursor.
++ Close the call hierarchy for the symbol under
+ the cursor.
+
+You can display either the incoming call hierarchy or the outgoing call
+hierarchy in this window. You cannot display both at the same time.
+
+In the call hierarchy tree window, the following commands are supported:
+
+ *:LspCallHierarchyRefresh*
+:LspCallHierarchyRefresh Query the language server again for the top
+ level symbol and refresh the call hierarchy
+ tree.
+ *:LspCallHierarchyIncoming*
+:LspCallHierarchyIncoming Display the incoming call hierarchy for the
+ top level symbol. If the window is currently
+ displaying the outgoing calls, then it is
+ refreshed to display the incoming calls.
+ *:LspCallHierarchyOutgoing*
+:LspCallHierarchyOutgoing Display the outgoing call hierarchy for the
+ top level symbol. If the window is currently
+ displaying the incoming calls, then it is
+ refreshed to display the outgoing calls.
+
+==============================================================================
+11. Autocommands *lsp-autocmds*
+
+ *LspSetup*
+LspSetup A |User| autocommand fired when the LSP plugin
+ is loaded. Can be used to add language
+ servers using the |LspAddServer()| function
+ and to set plugin options using the
+ |LspOptionsSet()| function.
+
+ *LspAttached*
+LspAttached A |User| autocommand fired when the LSP client
+ attaches to a buffer. Can be used to configure
+ buffer-local mappings or options.
+
+ *LspDiagsUpdated*
+LspDiagsUpdated A |User| autocommand invoked when new
+ diagnostics are received from the language
+ server. This is invoked after the LSP client
+ has processed the diagnostics. The function
+ lsp#diag#GetDiagsForBuf() can be used to get
+ all the diagnostics for a buffer.
+
+==============================================================================
+12. Highlight Groups *lsp-highlight-groups*
+
+The following highlight groups are used by the LSP plugin. You can define
+these highlight groups in your .vimrc file before sourcing this plugin to
+override them.
+
+*LspDiagInlineError* Used to highlight inline error diagnostics.
+ By default, linked to the "SpellBad" highlight
+ group.
+*LspDiagInlineHint* Used to highlight inline hint diagnostics.
+ By default, linked to the "SpellLocal"
+ highlight group.
+*LspDiagInlineInfo* Used to highlight inline info diagnostics.
+ By default, linked to the "SpellRare"
+ highlight group.
+*LspDiagInlineWarning* Used to highlight inline warning diagnostics.
+ By default, linked to the "SpellCap" highlight
+ group.
+*LspDiagLine* Used to highlight a line with one or more
+ diagnostics. By default linked to "NONE"
+ (cleared). You can link this to a highlight
+ group to highlight the line.
+*LspDiagSignErrorText* Used to highlight the sign text for error
+ diags. By default linked to 'ErrorMsg'.
+*LspDiagSignHintText* Used to highlight the sign text for hint
+ diags. By default linked to 'Question'.
+*LspDiagSignInfoText* Used to highlight the sign text for info
+ diags. By default linked to 'Pmenu'.
+*LspDiagSignWarningText* Used to highlight the sign text for warning
+ diags. By default linked to 'Search'.
+*LspDiagVirtualText* Used to highlight diagnostic virtual text.
+ By default, linked to the "LineNr" highlight
+ group.
+*LspDiagVirtualTextError* Used to highlight virtual text for error diags.
+ By default, linked to the "SpellBad" highlight
+ group.
+*LspDiagVirtualTextHint* Used to highlight virtual text for hint
+ diags. By default, linked to the "SpellLocal"
+ highlight group.
+*LspDiagVirtualTextInfo* Used to highlight virtual text for info
+ diags. By default, linked to the "SpellRare"
+ highlight group.
+*LspDiagVirtualTextWarning* Used to highlight virtual text for warning
+ diags. By default, linked to the "SpellCap"
+ highlight group.
+*LspInlayHintsParam* Used to highlight inlay hints of kind
+ "parameter". By default, linked to the
+ "Label" highlight group.
+*LspInlayHintsType* Used to highlight inlay hints of kind "type".
+ By default, linked to the "Conceal" highlight
+ group.
+*LspSigActiveParameter* Used to highlight the active signature
+ parameter. By default, linked to the "LineNr"
+ highlight group.
+*LspSymbolName* Used to highlight the symbol name when using
+ the |:LspDocumentSymbol| command. By default,
+ linked to the "Search" highlight group.
+*LspSymbolRange* Used to highlight the range of lines
+ containing a symbol when using the
+ |:LspDocumentSymbol| command. By default,
+ linked to the "Visual" highlight group.
+
+For example, to override the highlight used for diagnostics virtual text, you
+can use the following: >
+
+ highlight LspDiagVirtualText ctermfg=Cyan guifg=Blue
+<
+or >
+
+ highlight link LspDiagLine DiffAdd
+ highlight link LspDiagVirtualText WarningMsg
+<
+==============================================================================
+13. Debugging *lsp-debug*
+
+To debug this plugin, you can log the language server protocol messages sent
+and received by the plugin from the language server. The following command
+enables the logging of the messages from the language server for the current
+buffer: >
+
+ :LspServer debug on
+<
+This command also clears the log files. The following command disables the
+logging of the messages from the language server for the current buffer: >
+
+ :LspServer debug off
+<
+By default, the messages are not logged. Another method to enable the debug
+is to set the "debug" field to true when adding a language server
+using |LspAddServer()|.
+
+The messages printed by the language server in the stdout are logged to the
+lsp-<server-name>.log file and the messages printed in the stderr are logged
+to the lsp-<server-name>.err file. On a Unix-like system, these files are
+created in the /tmp directory. On MS-Windows, these files are created in the
+%TEMP% directory.
+
+The following command opens the file containing the messages printed by the
+language server in the stdout: >
+
+ :LspServer debug messages
+<
+The following command opens the file containing the messages printed by the
+language server in the stderr: >
+
+ :LspServer debug errors
+<
+To debug language server initialization problems, after enabling the above
+server debug, you can restart the server for the file type in the current
+buffer using the following command: >
+
+ :LspServer restart
+<
+The language servers typically support command line options to enable debug
+messages and to increase the verbosity of the messages. You can refer to the
+language server documentation for information about this. You can include
+these options when registering the language server with this plugin.
+
+If a language server supports the "$/logTrace" LSP notification, then you can
+use the :LspServerTrace command to set the server trace value: >
+
+ :LspServer trace { off | messages | verbose }
+<
+==============================================================================
+14. Custom Command Handlers *lsp-custom-commands*
+
+When applying a code action, the language server may issue a non-standard
+command. For example, the Java language server uses non-standard commands
+(e.g. java.apply.workspaceEdit). To handle these commands, you can register a
+callback function for each command using the LspRegisterCmdHandler() function.
+For example: >
+
+ vim9script
+ import autoload "lsp/textedit.vim"
+
+ def WorkspaceEdit(cmd: dict<any>)
+ for editAct in cmd.arguments
+ textedit.ApplyWorkspaceEdit(editAct)
+ endfor
+ enddef
+ g:LspRegisterCmdHandler('java.apply.workspaceEdit', WorkspaceEdit)
+<
+Place the above code in a file named lsp_java/plugin/lsp_java.vim and load
+this plugin.
+
+The callback function should accept a Dict argument. The Dict argument
+contains the LSP Command interface fields. Refer to the LSP specification for
+more information about the "Command" interface.
+
+==============================================================================
+15. Custom LSP Completion Kinds *lsp-custom-kinds*
+
+When a completion popup is triggered, the LSP client will use a default kind
+list to show in the completion "kind" section, to customize it, you need to
+use the option |lsp-opt-customCompletionKinds| and set all custom kinds in the
+option |lsp-opt-completionKinds| . There is a table with all default LSP
+kinds:
+
+ Kind Name | Value
+------------------------|--------------------
+ Text | t
+ Method | m
+ Function | f
+ Constructor | C
+ Field | F
+ Variable | v
+ Class | c
+ Interface | i
+ Module | M
+ Property | p
+ Unit | u
+ Value | V
+ Enum | e
+ Keyword | k
+ Snippet | S
+ Color | C
+ File | f
+ Reference | r
+ Folder | F
+ EnumMember | E
+ Constant | d
+ Struct | s
+ Event | E
+ Operator | o
+ TypeParameter | T
+ Buffer | B
+
+For example, if you want to change the "Method" kind to the kind "method()": >
+
+ vim9script
+
+ g:LspOptionsSet({
+ customCompletionKinds: true,
+ completionKinds: {
+ "Method": "method()"
+ }
+ })
+<
+In the completion popup, will show something like this: >
+
+ var file = new File()
+
+ file.cre
+ | create method() |
+ | createIfNotExists method() |
+ | ... |
+<
+==============================================================================
+16. Multiple Language Servers for a buffer *lsp-multiple-servers*
+
+It's possible to run multiple language servers for a given buffer.
+
+By default the language server defined first will be used for as much as it
+supports, then the next and so on. With the exception that diagnostics from
+all running language servers will be combined.
+
+This means that you can define a language server that only supports a subset
+of features at first and then define the general purpose language server after
+it:
+>
+ vim9script
+
+ g:LspAddServer([
+ # This language server reports that it only supports
+ # textDocument/documentFormatting, so it will be used
+ # for :LspFormat but nothing else.
+ {
+ filetype: ['html'],
+ path: 'html-pretty-lsp',
+ args: ['--stdio']
+ },
+ # This language server also supports
+ # textDocument/documentFormatting, but since it's been
+ # defined later, the one above will be used instead.
+ # However this server also supports
+ # textDocument/definition, textDocument/declaration,
+ # etc, so it will be used for :LspGotoDefinition,
+ # :LspGotoDeclaration, etc
+ {
+ filetype: ['html'],
+ path: 'html-language-server',
+ args: ['--stdio']
+ }
+ ])
+<
+As shown in the example above the order of when the language servers are being
+defined is taken into account for a given method. However sometimes the
+language server that you want to use for formatting also reports that it
+supports other features. In such a case you can do one of two things:
+
+1. change the order of language servers, and specify that a given language
+server should be used for a given method.
+
+2. set the unwanted features to |false| in the features |Dictionary| >
+
+ features: { 'codeAction': false }
+<
+For example, if you want to use the efm-langserver for formatting, but the
+typescript-language-server for everything else: >
+
+ vim9script
+
+ g:LspAddServer([
+ # this language server will be used by default, as it's defined
+ # as the first LSP for 'javascript' and 'typescript'
+ {
+ filetype: ['javascript', 'typescript'],
+ path: '/usr/local/bin/typescript-language-server',
+ args: ['--stdio']
+ },
+ # this language server will be used for documentFormatting
+ {
+ filetype: ['javascript', 'typescript'],
+ path: '/usr/local/bin/efm-langserver',
+ args: [],
+ features: {
+ documentFormatting: true
+ }
+ }
+ ])
+<
+Another way is to disable the unwanted features: for example if you don't want
+diagnostics from the typescript-language-server, but want to use it for
+everything else: >
+
+ vim9script
+
+ g:LspAddServer([
+ {
+ filetype: ['javascript', 'typescript'],
+ path: '/usr/local/bin/typescript-language-server',
+ args: ['--stdio'],
+ features: {
+ diagnostics: false
+ }
+ },
+ ])
+<
+==============================================================================
+17. Language Server Features *lsp-features*
+
+When using multiple language servers for a given file type, by providing the
+configuration |lsp-cfg-features| it is possible to specify which language
+server should be used for a given method/functionality. The following feature
+flags are supported: See |lsp-multiple-servers| for examples.
+
+ *lsp-features-callHierarchy*
+callHierarchy Used by the|:LspIncomingCalls| and the
+ |:LspOutgoingCalls| commands.
+ *lsp-features-codeAction*
+codeAction Used by the |:LspCodeAction| command.
+ *lsp-features-codeLens*
+codeLens Used by the |:LspCodeLens| command.
+ *lsp-features-completion*
+completion Used by 24/7 Completion and 'omnifunc'
+ *lsp-features-declaration*
+declaration Used by the |:LspGotoDeclaration|, and
+ the |:LspPeekDeclaration| commands.
+ *lsp-features-definition*
+definition Used by the|:LspGotoDefinition|, and
+ the |:LspPeekDefinition| commands.
+ *lsp-features-diagnostics*
+diagnostics Used to disable diagnostics for a single
+ language server, by default diagnostics are
+ combined from all running servers, by setting
+ this to |false| you can ignore diagnostics
+ from a specific server.
+ *lsp-features-documentFormatting*
+documentFormatting Used by the |:LspFormat| command, and
+ 'formatexpr'
+ *lsp-features-documentHighlight*
+documentHighlight Used by the |:LspHighlight| and the
+ |:LspHighlightClear| commands.
+ *lsp-features-documentSymbol*
+documentSymbol Used by the |:LspDocumentSymbol| and the
+ |:LspOutline| commands.
+ *lsp-features-foldingRange*
+foldingRange Used by the|:LspFold| command.
+ *lsp-features-hover*
+hover Used by the |:LspHover| command.
+ *lsp-features-implementation*
+implementation Used by the |:LspGotoImpl| and the
+ |:LspPeekImpl| commands.
+ *lsp-features-inlayHint*
+inlayHint Used to show the inlay hints for
+ function/method arguments.
+ *lsp-features-references*
+references Used by the |:LspShowReferences| command.
+ *lsp-features-rename*
+rename Used by the |:LspRename| command.
+ *lsp-features-selectionRange*
+selectionRange Used by the |:LspSelectionExpand| and the
+ |:LspSelectionShrink| commands.
+ *lsp-features-signatureHelp*
+signatureHelp Used by the |:LspShowSignature| command.
+ *lsp-features-typeDefinition*
+typeDefinition Used by the |:LspGotoTypeDef| and the
+ |:LspPeekTypeDef| commands.
+typeHierarchy Used by the |:LspSubTypeHierarchy| and the
+ |:LspSuperTypeHiearchy| commands.
+workspaceSymbol Used by the |:LspSymbolSearch| command.
+
+==============================================================================
+ *lsp-license*
+License: MIT License
+Copyright (c) 2020-2023 Yegappan Lakshmanan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+==============================================================================
+
+vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/vim/pack/downloads/opt/lsp/ftplugin/lspgfm.vim b/vim/pack/downloads/opt/lsp/ftplugin/lspgfm.vim
new file mode 100644
index 0000000..2e9bf76
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/ftplugin/lspgfm.vim
@@ -0,0 +1,78 @@
+vim9script
+
+import autoload 'lsp/markdown.vim' as md
+
+# Update the preview window with the github flavored markdown text
+def UpdatePreviewWindowContents(bnr: number, contentList: list<dict<any>>)
+ :silent! bnr->deletebufline(1, '$')
+
+ var lines: list<string> = []
+ var props: dict<list<list<number>>>
+ var lnum = 0
+
+ # Each item in "contentList" is a Dict with the following items:
+ # text: text for this line
+ # props: list of text properties. Each list item is a Dict. See
+ # |popup-props| for more information.
+ #
+ # Need to convert the text properties from the format used by
+ # popup_settext() to that used by prop_add_list().
+ for entry in contentList
+ lines->add(entry.text)
+ lnum += 1
+ if entry->has_key('props')
+ for p in entry.props
+ if !props->has_key(p.type)
+ props[p.type] = []
+ endif
+ if p->has_key('end_lnum')
+ props[p.type]->add([lnum, p.col, p.end_lnum, p.end_col])
+ else
+ props[p.type]->add([lnum, p.col, lnum, p.col + p.length])
+ endif
+ endfor
+ endif
+ endfor
+ setbufline(bnr, 1, lines)
+ for prop_type in props->keys()
+ prop_add_list({type: prop_type}, props[prop_type])
+ endfor
+enddef
+
+# Render the github flavored markdown text.
+# Text can be displayed either in a popup window or in a preview window.
+def RenderGitHubMarkdownText()
+ var bnr: number = bufnr()
+ var winId: number = win_getid()
+ var document: dict<list<any>>
+ var inPreviewWindow = false
+
+ if win_gettype() == 'preview'
+ inPreviewWindow = true
+ endif
+
+ try
+ if !inPreviewWindow
+ winId = bnr->getbufinfo()[0].popups[0]
+ endif
+ # parse the github markdown content and convert it into a list of text and
+ # list of associated text properties.
+ document = md.ParseMarkdown(bnr->getbufline(1, '$'), winId->winwidth())
+ catch /.*/
+ b:markdown_fallback = v:true
+ return
+ endtry
+
+ b:lsp_syntax = document.syntax
+ md.list_pattern->setbufvar(bnr, '&formatlistpat')
+ var settings = 'linebreak breakindent breakindentopt=list:-1'
+ win_execute(winId, $'setlocal {settings}')
+ if inPreviewWindow
+ UpdatePreviewWindowContents(bnr, document.content)
+ else
+ winId->popup_settext(document.content)
+ endif
+enddef
+RenderGitHubMarkdownText()
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/plugin/lsp.vim b/vim/pack/downloads/opt/lsp/plugin/lsp.vim
new file mode 100644
index 0000000..3b3815a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/plugin/lsp.vim
@@ -0,0 +1,163 @@
+if !has('vim9script') || v:version < 900
+ " Needs Vim version 9.0 and above
+ finish
+endif
+
+vim9script noclear
+
+# Language Server Protocol (LSP) plugin for vim
+
+if get(g:, 'loaded_lsp', false)
+ finish
+endif
+g:loaded_lsp = true
+
+import '../autoload/lsp/options.vim'
+import autoload '../autoload/lsp/lsp.vim'
+
+# Set LSP plugin options from 'opts'.
+def g:LspOptionsSet(opts: dict<any>)
+ options.OptionsSet(opts)
+enddef
+
+# Return a copy of all the LSP plugin options
+def g:LspOptionsGet(): dict<any>
+ return options.OptionsGet()
+enddef
+
+# Add one or more LSP servers in 'serverList'
+def g:LspAddServer(serverList: list<dict<any>>)
+ lsp.AddServer(serverList)
+enddef
+
+# Register 'Handler' callback function for LSP command 'cmd'.
+def g:LspRegisterCmdHandler(cmd: string, Handler: func)
+ lsp.RegisterCmdHandler(cmd, Handler)
+enddef
+
+# Returns true if the language server for the current buffer is initialized
+# and ready to accept requests.
+def g:LspServerReady(): bool
+ return lsp.ServerReady()
+enddef
+
+# Returns true if the language server for 'ftype' file type is running
+def g:LspServerRunning(ftype: string): bool
+ return lsp.ServerRunning(ftype)
+enddef
+
+augroup LSPAutoCmds
+ au!
+ autocmd BufNewFile,BufReadPost,FileType * lsp.AddFile(expand('<abuf>')->str2nr())
+ # Note that when BufWipeOut is invoked, the current buffer may be different
+ # from the buffer getting wiped out.
+ autocmd BufWipeOut * lsp.RemoveFile(expand('<abuf>')->str2nr())
+ autocmd BufWinEnter * lsp.BufferLoadedInWin(expand('<abuf>')->str2nr())
+augroup END
+
+# TODO: Is it needed to shutdown all the LSP servers when exiting Vim?
+# This takes some time.
+# autocmd VimLeavePre * call lsp.StopAllServers()
+
+# LSP commands
+command! -nargs=? -bar -range LspCodeAction lsp.CodeAction(<line1>, <line2>, <q-args>)
+command! -nargs=0 -bar LspCodeLens lsp.CodeLens()
+command! -nargs=+ -bar -bang -count -complete=customlist,lsp.LspDiagComplete LspDiag lsp.LspDiagCmd(<q-args>, <count>, <bang>false)
+command! -nargs=0 -bar -bang LspDiagCurrent lsp.LspShowCurrentDiag(<bang>false)
+command! -nargs=0 -bar LspDiagFirst lsp.JumpToDiag('first')
+command! -nargs=0 -bar LspDiagLast lsp.JumpToDiag('last')
+command! -nargs=0 -bar -count=1 LspDiagNext lsp.JumpToDiag('next', <count>)
+command! -nargs=0 -bar -count=1 LspDiagNextWrap lsp.JumpToDiag('nextWrap', <count>)
+command! -nargs=0 -bar -count=1 LspDiagPrev lsp.JumpToDiag('prev', <count>)
+command! -nargs=0 -bar -count=1 LspDiagPrevWrap lsp.JumpToDiag('prevWrap', <count>)
+command! -nargs=0 -bar LspDiagShow lsp.ShowDiagnostics()
+command! -nargs=0 -bar LspDiagHere lsp.JumpToDiag('here')
+command! -nargs=0 -bar LspDocumentSymbol lsp.ShowDocSymbols()
+command! -nargs=0 -bar LspFold lsp.FoldDocument()
+command! -nargs=0 -bar -range=% LspFormat lsp.TextDocFormat(<range>, <line1>, <line2>)
+command! -nargs=0 -bar -count LspGotoDeclaration lsp.GotoDeclaration(v:false, <q-mods>, <count>)
+command! -nargs=0 -bar -count LspGotoDefinition lsp.GotoDefinition(v:false, <q-mods>, <count>)
+command! -nargs=0 -bar -count LspGotoImpl lsp.GotoImplementation(v:false, <q-mods>, <count>)
+command! -nargs=0 -bar -count LspGotoTypeDef lsp.GotoTypedef(v:false, <q-mods>, <count>)
+command! -nargs=0 -bar LspHighlight call LspDocHighlight(bufnr(), <q-mods>)
+command! -nargs=0 -bar LspHighlightClear call LspDocHighlightClear()
+command! -nargs=? -bar LspHover lsp.Hover(<q-mods>)
+command! -nargs=1 -bar -complete=customlist,lsp.LspInlayHintsComplete LspInlayHints lsp.InlayHints(<q-args>)
+command! -nargs=0 -bar LspIncomingCalls lsp.IncomingCalls()
+command! -nargs=0 -bar LspOutgoingCalls lsp.OutgoingCalls()
+command! -nargs=0 -bar -count LspOutline lsp.Outline(<q-mods>, <count>)
+command! -nargs=0 -bar -count LspPeekDeclaration lsp.GotoDeclaration(v:true, <q-mods>, <count>)
+command! -nargs=0 -bar -count LspPeekDefinition lsp.GotoDefinition(v:true, <q-mods>, <count>)
+command! -nargs=0 -bar -count LspPeekImpl lsp.GotoImplementation(v:true, <q-mods>, <count>)
+command! -nargs=0 -bar LspPeekReferences lsp.ShowReferences(v:true)
+command! -nargs=0 -bar -count LspPeekTypeDef lsp.GotoTypedef(v:true, <q-mods>, <count>)
+command! -nargs=? -bar LspRename lsp.Rename(<q-args>)
+command! -nargs=0 -bar LspSelectionExpand lsp.SelectionExpand()
+command! -nargs=0 -bar LspSelectionShrink lsp.SelectionShrink()
+command! -nargs=+ -bar -complete=customlist,lsp.LspServerComplete LspServer lsp.LspServerCmd(<q-args>)
+command! -nargs=0 -bar LspShowReferences lsp.ShowReferences(v:false)
+command! -nargs=0 -bar LspShowAllServers lsp.ShowAllServers()
+command! -nargs=0 -bar LspShowSignature call LspShowSignature()
+command! -nargs=0 -bar LspSubTypeHierarchy lsp.TypeHierarchy(0)
+command! -nargs=0 -bar LspSuperTypeHierarchy lsp.TypeHierarchy(1)
+# Clangd specifc extension to switch from one C/C++ source file to a
+# corresponding header file
+command! -nargs=0 -bar LspSwitchSourceHeader lsp.SwitchSourceHeader()
+command! -nargs=? -bar LspSymbolSearch lsp.SymbolSearch(<q-args>, <q-mods>)
+command! -nargs=1 -bar -complete=dir LspWorkspaceAddFolder lsp.AddWorkspaceFolder(<q-args>)
+command! -nargs=0 -bar LspWorkspaceListFolders lsp.ListWorkspaceFolders()
+command! -nargs=1 -bar -complete=dir LspWorkspaceRemoveFolder lsp.RemoveWorkspaceFolder(<q-args>)
+
+# Add the GUI menu entries
+if has('gui_running')
+ anoremenu <silent> L&sp.Goto.Definition :LspGotoDefinition<CR>
+ anoremenu <silent> L&sp.Goto.Declaration :LspGotoDeclaration<CR>
+ anoremenu <silent> L&sp.Goto.Implementation :LspGotoImpl<CR>
+ anoremenu <silent> L&sp.Goto.TypeDef :LspGotoTypeDef<CR>
+
+ anoremenu <silent> L&sp.Show\ Signature :LspShowSignature<CR>
+ anoremenu <silent> L&sp.Show\ References :LspShowReferences<CR>
+ anoremenu <silent> L&sp.Show\ Detail :LspHover<CR>
+ anoremenu <silent> L&sp.Outline :LspOutline<CR>
+
+ anoremenu <silent> L&sp.Goto\ Symbol :LspDocumentSymbol<CR>
+ anoremenu <silent> L&sp.Symbol\ Search :LspSymbolSearch<CR>
+ anoremenu <silent> L&sp.Outgoing\ Calls :LspOutgoingCalls<CR>
+ anoremenu <silent> L&sp.Incoming\ Calls :LspIncomingCalls<CR>
+ anoremenu <silent> L&sp.Rename :LspRename<CR>
+ anoremenu <silent> L&sp.Code\ Action :LspCodeAction<CR>
+
+ anoremenu <silent> L&sp.Highlight\ Symbol :LspHighlight<CR>
+ anoremenu <silent> L&sp.Highlight\ Clear :LspHighlightClear<CR>
+
+ # Diagnostics
+ anoremenu <silent> L&sp.Diagnostics.Current :LspDiag current<CR>
+ anoremenu <silent> L&sp.Diagnostics.Show\ All :LspDiag show<CR>
+ anoremenu <silent> L&sp.Diagnostics.First :LspDiag first<CR>
+ anoremenu <silent> L&sp.Diagnostics.Last :LspDiag last<CR>
+ anoremenu <silent> L&sp.Diagnostics.Next :LspDiag next<CR>
+ anoremenu <silent> L&sp.Diagnostics.Previous :LspDiag prev<CR>
+ anoremenu <silent> L&sp.Diagnostics.This :LspDiag here<CR>
+
+ if &mousemodel =~ 'popup'
+ anoremenu <silent> PopUp.L&sp.Go\ to\ Definition
+ \ :LspGotoDefinition<CR>
+ anoremenu <silent> PopUp.L&sp.Go\ to\ Declaration
+ \ :LspGotoDeclaration<CR>
+ anoremenu <silent> PopUp.L&sp.Find\ All\ References
+ \ :LspShowReferences<CR>
+ anoremenu <silent> PopUp.L&sp.Show\ Detail
+ \ :LspHover<CR>
+ anoremenu <silent> PopUp.L&sp.Highlight\ Symbol
+ \ :LspHighlight<CR>
+ anoremenu <silent> PopUp.L&sp.Highlight\ Clear
+ \ :LspHighlightClear<CR>
+ endif
+endif
+
+# Invoke autocmd to register LSP servers and to set LSP options
+if exists('#User#LspSetup')
+ :doautocmd <nomodeline> User LspSetup
+endif
+
+# vim: shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/syntax/lspgfm.vim b/vim/pack/downloads/opt/lsp/syntax/lspgfm.vim
new file mode 100644
index 0000000..9e71010
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/syntax/lspgfm.vim
@@ -0,0 +1,23 @@
+vim9script
+
+if get(b:, 'markdown_fallback', v:false)
+ runtime! syntax/markdown.vim
+ finish
+endif
+
+var group: dict<string> = {}
+for region in get(b:, 'lsp_syntax', [])
+ if !group->has_key(region.lang)
+ group[region.lang] = region.lang->substitute('\(^.\|_\a\)', '\u&', 'g')
+ try
+ exe $'syntax include @{group[region.lang]} syntax/{region.lang}.vim'
+ catch /.*/
+ group[region.lang] = ''
+ endtry
+ endif
+ if !group[region.lang]->empty()
+ exe $'syntax region lspCodeBlock start="{region.start}" end="{region.end}" contains=@{group[region.lang]}'
+ endif
+endfor
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim b/vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim
new file mode 100644
index 0000000..397f459
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim
@@ -0,0 +1,478 @@
+vim9script
+# Unit tests for language server protocol offset encoding using clangd
+
+source common.vim
+
+# Start the C language server. Returns true on success and false on failure.
+def g:StartLangServer(): bool
+ if has('patch-9.0.1629')
+ return g:StartLangServerWithFile('Xtest.c')
+ endif
+ return false
+enddef
+
+if !has('patch-9.0.1629')
+ # Need patch 9.0.1629 to properly encode/decode the UTF-16 offsets
+ finish
+endif
+
+var lspOpts = {autoComplete: false}
+g:LspOptionsSet(lspOpts)
+
+var lspServers = [{
+ filetype: ['c', 'cpp'],
+ path: (exepath('clangd-15') ?? exepath('clangd')),
+ args: ['--background-index',
+ '--clang-tidy',
+ $'--offset-encoding={$LSP_OFFSET_ENCODING}']
+ }]
+call LspAddServer(lspServers)
+
+# Test for :LspCodeAction with symbols containing multibyte and composing
+# characters
+def g:Test_LspCodeAction_multibyte()
+ silent! edit XLspCodeAction_mb.c
+ sleep 200m
+ var lines =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("😊😊😊😊 = %d\n", aVar):
+ printf("áb́áb́ = %d\n", aVar):
+ printf("ą́ą́ą́ą́ = %d\n", aVar):
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(3)
+ :redraw!
+ cursor(5, 5)
+ redraw!
+ :LspCodeAction 1
+ assert_equal(' printf("😊😊😊😊 = %d\n", aVar);', getline(5))
+ cursor(6, 5)
+ redraw!
+ :LspCodeAction 1
+ assert_equal(' printf("áb́áb́ = %d\n", aVar);', getline(6))
+ cursor(7, 5)
+ redraw!
+ :LspCodeAction 1
+ assert_equal(' printf("ą́ą́ą́ą́ = %d\n", aVar);', getline(7))
+
+ :%bw!
+enddef
+
+# Test for ":LspDiag show" when using multibyte and composing characters
+def g:Test_LspDiagShow_multibyte()
+ :silent! edit XLspDiagShow_mb.c
+ sleep 200m
+ var lines =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("😊😊😊😊 = %d\n". aVar);
+ printf("áb́áb́ = %d\n". aVar);
+ printf("ą́ą́ą́ą́ = %d\n". aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(3)
+ :redraw!
+ :LspDiag show
+ var qfl: list<dict<any>> = getloclist(0)
+ assert_equal([5, 37], [qfl[0].lnum, qfl[0].col])
+ assert_equal([6, 33], [qfl[1].lnum, qfl[1].col])
+ assert_equal([7, 41], [qfl[2].lnum, qfl[2].col])
+ :lclose
+ :%bw!
+enddef
+
+# Test for :LspFormat when using multibyte and composing characters
+def g:Test_LspFormat_multibyte()
+ :silent! edit XLspFormat_mb.c
+ sleep 200m
+ var lines =<< trim END
+ void fn(int aVar)
+ {
+ int 😊😊😊😊 = aVar + 1;
+ int áb́áb́ = aVar + 1;
+ int ą́ą́ą́ą́ = aVar + 1;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ :redraw!
+ :LspFormat
+ var expected =<< trim END
+ void fn(int aVar) {
+ int 😊😊😊😊 = aVar + 1;
+ int áb́áb́ = aVar + 1;
+ int ą́ą́ą́ą́ = aVar + 1;
+ }
+ END
+ assert_equal(expected, getline(1, '$'))
+ :%bw!
+enddef
+
+# Test for :LspGotoDefinition when using multibyte and composing characters
+def g:Test_LspGotoDefinition_multibyte()
+ :silent! edit XLspGotoDefinition_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("😊😊😊😊 = %d\n", aVar);
+ printf("áb́áb́ = %d\n", aVar);
+ printf("ą́ą́ą́ą́ = %d\n", aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ for [lnum, colnr] in [[4, 27], [5, 39], [6, 35], [7, 43]]
+ cursor(lnum, colnr)
+ :LspGotoDefinition
+ assert_equal([2, 13], [line('.'), col('.')])
+ endfor
+
+ :%bw!
+enddef
+
+# Test for :LspGotoDefinition when using multibyte and composing characters
+def g:Test_LspGotoDefinition_after_multibyte()
+ :silent! edit XLspGotoDef_after_mb.c
+ sleep 200m
+ var lines =<< trim END
+ void fn(int aVar)
+ {
+ /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int αβγδ, bVar;
+ /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int 😊😊😊😊, cVar;
+ /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int áb́áb́, dVar;
+ /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int ą́ą́ą́ą́, eVar;
+ bVar = 1;
+ cVar = 2;
+ dVar = 3;
+ eVar = 4;
+ aVar = αβγδ + 😊😊😊😊 + áb́áb́ + ą́ą́ą́ą́ + bVar;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ :redraw!
+ cursor(7, 5)
+ :LspGotoDefinition
+ assert_equal([3, 88], [line('.'), col('.')])
+ cursor(8, 5)
+ :LspGotoDefinition
+ assert_equal([4, 96], [line('.'), col('.')])
+ cursor(9, 5)
+ :LspGotoDefinition
+ assert_equal([5, 92], [line('.'), col('.')])
+ cursor(10, 5)
+ :LspGotoDefinition
+ assert_equal([6, 100], [line('.'), col('.')])
+ cursor(11, 12)
+ :LspGotoDefinition
+ assert_equal([3, 78], [line('.'), col('.')])
+ cursor(11, 23)
+ :LspGotoDefinition
+ assert_equal([4, 78], [line('.'), col('.')])
+ cursor(11, 42)
+ :LspGotoDefinition
+ assert_equal([5, 78], [line('.'), col('.')])
+ cursor(11, 57)
+ :LspGotoDefinition
+ assert_equal([6, 78], [line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# Test for doing omni completion for symbols with multibyte and composing
+# characters
+def g:Test_OmniComplete_multibyte()
+ :silent! edit XOmniComplete_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void Func1(void)
+ {
+ int 😊😊😊😊, aVar;
+ int áb́áb́, bVar;
+ int ą́ą́ą́ą́, cVar;
+
+
+
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(6, 4)
+ feedkeys("aaV\<C-X>\<C-O> = 😊😊\<C-X>\<C-O>;", 'xt')
+ assert_equal(' aVar = 😊😊😊😊;', getline('.'))
+ cursor(7, 4)
+ feedkeys("abV\<C-X>\<C-O> = áb́\<C-X>\<C-O>;", 'xt')
+ assert_equal(' bVar = áb́áb́;', getline('.'))
+ cursor(8, 4)
+ feedkeys("acV\<C-X>\<C-O> = ą́ą́\<C-X>\<C-O>;", 'xt')
+ assert_equal(' cVar = ą́ą́ą́ą́;', getline('.'))
+ feedkeys("oáb́\<C-X>\<C-O> = ą́ą́\<C-X>\<C-O>;", 'xt')
+ assert_equal(' áb́áb́ = ą́ą́ą́ą́;', getline('.'))
+ feedkeys("oą́ą́\<C-X>\<C-O> = áb́\<C-X>\<C-O>;", 'xt')
+ assert_equal(' ą́ą́ą́ą́ = áb́áb́;', getline('.'))
+ :%bw!
+enddef
+
+# Test for :LspOutline with multibyte and composing characters
+def g:Test_Outline_multibyte()
+ silent! edit XLspOutline_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ typedef void 😊😊😊😊;
+ typedef void áb́áb́;
+ typedef void ą́ą́ą́ą́;
+
+ 😊😊😊😊 Func1()
+ {
+ }
+
+ áb́áb́ Func2()
+ {
+ }
+
+ ą́ą́ą́ą́ Func3()
+ {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(1, 1)
+ :LspOutline
+ assert_equal(2, winnr('$'))
+
+ :wincmd w
+ cursor(5, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 5, 18], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(6, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 9, 14], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(7, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 13, 22], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(10, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 1, 14], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(11, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 2, 14], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(12, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 3, 14], [winnr(), line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# Test for :LspRename with multibyte and composing characters
+def g:Test_LspRename_multibyte()
+ silent! edit XLspRename_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("😊😊😊😊 = %d\n", aVar);
+ printf("áb́áb́ = %d\n", aVar);
+ printf("ą́ą́ą́ą́ = %d\n", aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+ cursor(2, 12)
+ :LspRename bVar
+ redraw!
+ var expected: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int bVar)
+ {
+ printf("aVar = %d\n", bVar);
+ printf("😊😊😊😊 = %d\n", bVar);
+ printf("áb́áb́ = %d\n", bVar);
+ printf("ą́ą́ą́ą́ = %d\n", bVar);
+ }
+ END
+ assert_equal(expected, getline(1, '$'))
+ :%bw!
+enddef
+
+# Test for :LspShowReferences when using multibyte and composing characters
+def g:Test_LspShowReferences_multibyte()
+ :silent! edit XLspShowReferences_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("😊😊😊😊 = %d\n", aVar);
+ printf("áb́áb́ = %d\n", aVar);
+ printf("ą́ą́ą́ą́ = %d\n", aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+ cursor(4, 27)
+ :LspShowReferences
+ var qfl: list<dict<any>> = getloclist(0)
+ assert_equal([2, 13], [qfl[0].lnum, qfl[0].col])
+ assert_equal([4, 27], [qfl[1].lnum, qfl[1].col])
+ assert_equal([5, 39], [qfl[2].lnum, qfl[2].col])
+ assert_equal([6, 35], [qfl[3].lnum, qfl[3].col])
+ assert_equal([7, 43], [qfl[4].lnum, qfl[4].col])
+ :lclose
+
+ :%bw!
+enddef
+
+# Test for :LspSymbolSearch when using multibyte and composing characters
+def g:Test_LspSymbolSearch_multibyte()
+ silent! edit XLspSymbolSearch_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ typedef void 😊😊😊😊;
+ typedef void áb́áb́;
+ typedef void ą́ą́ą́ą́;
+
+ 😊😊😊😊 Func1()
+ {
+ }
+
+ áb́áb́ Func2()
+ {
+ }
+
+ ą́ą́ą́ą́ Func3()
+ {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch Func1\<CR>", "xt")
+ assert_equal([5, 18], [line('.'), col('.')])
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch Func2\<CR>", "xt")
+ assert_equal([9, 14], [line('.'), col('.')])
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch Func3\<CR>", "xt")
+ assert_equal([13, 22], [line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# Test for setting the 'tagfunc' with multibyte and composing characters in
+# symbols
+def g:Test_LspTagFunc_multibyte()
+ var lines =<< trim END
+ void fn(int aVar)
+ {
+ int 😊😊😊😊, bVar;
+ int áb́áb́, cVar;
+ int ą́ą́ą́ą́, dVar;
+ bVar = 10;
+ cVar = 10;
+ dVar = 10;
+ }
+ END
+ writefile(lines, 'Xtagfunc_mb.c')
+ :silent! edit! Xtagfunc_mb.c
+ g:WaitForServerFileLoad(0)
+ :setlocal tagfunc=lsp#lsp#TagFunc
+ cursor(6, 5)
+ :exe "normal \<C-]>"
+ assert_equal([3, 27], [line('.'), col('.')])
+ cursor(7, 5)
+ :exe "normal \<C-]>"
+ assert_equal([4, 23], [line('.'), col('.')])
+ cursor(8, 5)
+ :exe "normal \<C-]>"
+ assert_equal([5, 31], [line('.'), col('.')])
+ :set tagfunc&
+
+ :%bw!
+ delete('Xtagfunc_mb.c')
+enddef
+
+# Test for the :LspSuperTypeHierarchy and :LspSubTypeHierarchy commands with
+# multibyte and composing characters
+def g:Test_LspTypeHier_multibyte()
+ silent! edit XLspTypeHier_mb.cpp
+ sleep 200m
+ var lines =<< trim END
+ /* αβ😊😊ááą́ą́ */ class parent {
+ };
+
+ /* αβ😊😊ááą́ą́ */ class child : public parent {
+ };
+
+ /* αβ😊😊ááą́ą́ */ class grandchild : public child {
+ };
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(1, 42)
+ :LspSubTypeHierarchy
+ call feedkeys("\<CR>", 'xt')
+ assert_equal([1, 36], [line('.'), col('.')])
+ cursor(1, 42)
+
+ :LspSubTypeHierarchy
+ call feedkeys("\<Down>\<CR>", 'xt')
+ assert_equal([4, 42], [line('.'), col('.')])
+
+ cursor(1, 42)
+ :LspSubTypeHierarchy
+ call feedkeys("\<Down>\<Down>\<CR>", 'xt')
+ assert_equal([7, 42], [line('.'), col('.')])
+
+ cursor(7, 42)
+ :LspSuperTypeHierarchy
+ call feedkeys("\<CR>", 'xt')
+ assert_equal([7, 36], [line('.'), col('.')])
+
+ cursor(7, 42)
+ :LspSuperTypeHierarchy
+ call feedkeys("\<Down>\<CR>", 'xt')
+ assert_equal([4, 36], [line('.'), col('.')])
+
+ cursor(7, 42)
+ :LspSuperTypeHierarchy
+ call feedkeys("\<Down>\<Down>\<CR>", 'xt')
+ assert_equal([1, 36], [line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/clangd_tests.vim b/vim/pack/downloads/opt/lsp/test/clangd_tests.vim
new file mode 100644
index 0000000..b084e86
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/clangd_tests.vim
@@ -0,0 +1,1782 @@
+vim9script
+# Unit tests for Vim Language Server Protocol (LSP) clangd client
+
+source common.vim
+
+var lspOpts = {autoComplete: false}
+g:LspOptionsSet(lspOpts)
+
+g:LSPTest_modifyDiags = false
+
+var lspServers = [{
+ filetype: ['c', 'cpp'],
+ path: (exepath('clangd-15') ?? exepath('clangd')),
+ args: ['--background-index', '--clang-tidy'],
+ initializationOptions: { clangdFileStatus: true },
+ customNotificationHandlers: {
+ 'textDocument/clangd.fileStatus': (lspserver: dict<any>, reply: dict<any>) => {
+ g:LSPTest_customNotificationHandlerReplied = true
+ }
+ },
+ processDiagHandler: (diags: list<dict<any>>) => {
+ if g:LSPTest_modifyDiags != true
+ return diags
+ endif
+
+ return diags->map((ix, diag) => {
+ diag.message = $'this is overridden'
+ return diag
+ })
+ }
+ }]
+call LspAddServer(lspServers)
+
+var clangdVerDetail = systemlist($'{shellescape(lspServers[0].path)} --version')
+var clangdVerMajor = clangdVerDetail->matchstr('.*version \d\+\..*')->substitute('.* \(\d\+\)\..*', '\1', 'g')->str2nr()
+echomsg clangdVerDetail
+
+
+# Test for formatting a file using LspFormat
+def g:Test_LspFormat()
+ :silent! edit XLspFormat.c
+ sleep 200m
+ setline(1, [' int i;', ' int j;'])
+ :redraw!
+ :LspFormat
+ assert_equal(['int i;', 'int j;'], getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ setline(1, ['int f1(int i)', '{', 'int j = 10; return j;', '}'])
+ :redraw!
+ :LspFormat
+ assert_equal(['int f1(int i) {', ' int j = 10;', ' return j;', '}'],
+ getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ setline(1, ['', 'int i;'])
+ :redraw!
+ :LspFormat
+ assert_equal(['', 'int i;'], getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ setline(1, [' int i;'])
+ :redraw!
+ :LspFormat
+ assert_equal(['int i;'], getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ setline(1, [' int i; '])
+ :redraw!
+ :LspFormat
+ assert_equal(['int i;'], getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ setline(1, ['int i;', '', '', ''])
+ :redraw!
+ :LspFormat
+ assert_equal(['int i;'], getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ setline(1, ['int f1(){int x;int y;x=1;y=2;return x+y;}'])
+ :redraw!
+ :LspFormat
+ var expected: list<string> =<< trim END
+ int f1() {
+ int x;
+ int y;
+ x = 1;
+ y = 2;
+ return x + y;
+ }
+ END
+ assert_equal(expected, getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ setline(1, ['', '', '', ''])
+ :redraw!
+ :LspFormat
+ assert_equal([''], getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ var lines: list<string> =<< trim END
+ int f1() {
+ int i, j;
+ for (i = 1; i < 10; i++) { j++; }
+ for (j = 1; j < 10; j++) { i++; }
+ }
+ END
+ setline(1, lines)
+ :redraw!
+ :4LspFormat
+ expected =<< trim END
+ int f1() {
+ int i, j;
+ for (i = 1; i < 10; i++) { j++; }
+ for (j = 1; j < 10; j++) {
+ i++;
+ }
+ }
+ END
+ assert_equal(expected, getline(1, '$'))
+
+ deletebufline('', 1, '$')
+ # shrinking multiple lines into a single one works
+ setline(1, ['int \', 'i \', '= \', '42;'])
+ :redraw!
+ :4LspFormat
+ assert_equal(['int i = 42;'], getline(1, '$'))
+ bw!
+
+ # empty file
+ assert_equal('', execute('LspFormat'))
+
+ # file without an LSP server
+ edit a.raku
+ assert_equal('Error: Language server for "raku" file type supporting "documentFormatting" feature is not found',
+ execute('LspFormat')->split("\n")[0])
+
+ :%bw!
+enddef
+
+# Test for formatting a file using 'formatexpr'
+def g:Test_LspFormatExpr()
+ :silent! edit XLspFormat.c
+ sleep 200m
+ setlocal formatexpr=lsp#lsp#FormatExpr()
+ setline(1, [' int i;', ' int j;'])
+ :redraw!
+ normal! ggVGgq
+ assert_equal(['int i;', 'int j;'], getline(1, '$'))
+
+ # empty line/file
+ deletebufline('', 1, '$')
+ setline(1, [''])
+ redraw!
+ normal! ggVGgq
+ assert_equal([''], getline(1, '$'))
+
+ setlocal formatexpr&
+ :%bw!
+enddef
+
+# Test for :LspShowReferences - showing all the references to a symbol in a
+# file using LSP
+def g:Test_LspShowReferences()
+ :silent! edit XshowRefs.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ int count;
+ void redFunc()
+ {
+ int count, i;
+ count = 10;
+ i = count;
+ }
+ void blueFunc()
+ {
+ int count, j;
+ count = 20;
+ j = count;
+ }
+ END
+ setline(1, lines)
+ :redraw!
+ cursor(5, 2)
+ var bnr: number = bufnr()
+ :LspShowReferences
+ sleep 100m
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ var loclist: list<dict<any>> = getloclist(0)
+ assert_equal(bnr, loclist[0].bufnr)
+ assert_equal(3, loclist->len())
+ assert_equal([4, 6], [loclist[0].lnum, loclist[0].col])
+ assert_equal([5, 2], [loclist[1].lnum, loclist[1].col])
+ assert_equal([6, 6], [loclist[2].lnum, loclist[2].col])
+ :lclose
+ cursor(1, 5)
+ :LspShowReferences
+ assert_equal(1, getloclist(0)->len())
+ loclist = getloclist(0)
+ assert_equal([1, 5], [loclist[0].lnum, loclist[0].col])
+ :lclose
+
+ # Test for opening in qf list
+ g:LspOptionsSet({useQuickfixForLocations: true})
+ cursor(5, 2)
+ :LspShowReferences
+ sleep 100m
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ :cclose
+ var qfl: list<dict<any>> = getqflist()
+ assert_equal(3, qfl->len())
+ assert_equal(bufnr(), qfl[0].bufnr)
+ assert_equal([4, 6], [qfl[0].lnum, qfl[0].col])
+ assert_equal([5, 2], [qfl[1].lnum, qfl[1].col])
+ assert_equal([6, 6], [qfl[2].lnum, qfl[2].col])
+ cursor(1, 5)
+ :LspShowReferences
+ assert_equal(1, getqflist()->len())
+ qfl = getqflist()
+ assert_equal([1, 5], [qfl[0].lnum, qfl[0].col])
+ :cclose
+ g:LspOptionsSet({useQuickfixForLocations: false})
+
+ # Test for maintaining buffer focus
+ g:LspOptionsSet({keepFocusInReferences: false})
+ :LspShowReferences
+ assert_equal('', getwinvar(0, '&buftype'))
+ :lclose
+ g:LspOptionsSet({keepFocusInReferences: true})
+
+ # Test for LspPeekReferences
+
+ # Opening the preview window with an unsaved buffer displays the "E37: No
+ # write since last change" error message. To disable this message, mark the
+ # buffer as not modified.
+ setlocal nomodified
+ cursor(10, 6)
+ :LspPeekReferences
+ sleep 50m
+ var ids = popup_list()
+ assert_equal(2, ids->len())
+ var filePopupAttrs = ids[0]->popup_getoptions()
+ var refPopupAttrs = ids[1]->popup_getoptions()
+ assert_match('XshowRefs', filePopupAttrs.title)
+ assert_equal('Symbol References', refPopupAttrs.title)
+ assert_equal(10, line('.', ids[0]))
+ assert_equal(1, line('.', ids[1]))
+ assert_equal(3, line('$', ids[1]))
+ feedkeys("jj\<CR>", 'xt')
+ assert_equal(12, line('.'))
+ assert_equal([], popup_list())
+ popup_clear()
+
+ # LspShowReferences should start with the current symbol
+ cursor(12, 6)
+ :LspPeekReferences
+ sleep 50m
+ ids = popup_list()
+ assert_equal(2, ids->len())
+ assert_equal(12, line('.', ids[0]))
+ assert_equal(3, line('.', ids[1]))
+ feedkeys("\<CR>", 'xt')
+ popup_clear()
+
+ bw!
+
+ # empty file
+ assert_equal('', execute('LspShowReferences'))
+
+ # file without an LSP server
+ edit a.raku
+ assert_equal('Error: Language server for "raku" file type supporting "references" feature is not found',
+ execute('LspShowReferences')->split("\n")[0])
+
+ :%bw!
+enddef
+
+# Test for LSP diagnostics
+def g:Test_LspDiag()
+ :silent! edit XLspDiag.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void blueFunc()
+ {
+ int count, j:
+ count = 20;
+ j <= count;
+ j = 10;
+ MyFunc();
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(1)
+ var bnr: number = bufnr()
+ :redraw!
+ :LspDiag show
+ var qfl: list<dict<any>> = getloclist(0)
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ assert_equal(bnr, qfl[0].bufnr)
+ assert_equal(3, qfl->len())
+ assert_equal([3, 14, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
+ assert_equal([5, 2, 'W'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
+ assert_equal([7, 2, 'W'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
+ close
+ g:LspOptionsSet({showDiagInPopup: false})
+ normal gg
+ var output = execute('LspDiag current')->split("\n")
+ assert_equal('Warn: No diagnostic messages found for current line', output[0])
+ :LspDiag first
+ assert_equal([3, 14], [line('.'), col('.')])
+ output = execute('LspDiag current')->split("\n")
+ assert_equal("Expected ';' at end of declaration (fix available)", output[0])
+ :normal! 0
+ :LspDiag here
+ assert_equal([3, 14], [line('.'), col('.')])
+ :LspDiag next
+ assert_equal([5, 2], [line('.'), col('.')])
+ :LspDiag next
+ assert_equal([7, 2], [line('.'), col('.')])
+ output = execute('LspDiag next')->split("\n")
+ assert_equal('Warn: No more diagnostics found', output[0])
+ :LspDiag prev
+ :LspDiag prev
+ :LspDiag prev
+ output = execute('LspDiag prev')->split("\n")
+ assert_equal('Warn: No more diagnostics found', output[0])
+
+ # Test for maintaining buffer focus
+ g:LspOptionsSet({keepFocusInDiags: false})
+ :LspDiag show
+ assert_equal('', getwinvar(0, '&buftype'))
+ :lclose
+ g:LspOptionsSet({keepFocusInDiags: true})
+
+ # :[count]LspDiag next
+ cursor(3, 1)
+ :2LspDiag next
+ assert_equal([5, 2], [line('.'), col('.')])
+ :2LspDiag next
+ assert_equal([7, 2], [line('.'), col('.')])
+ output = execute(':2LspDiag next')->split("\n")
+ assert_equal('Warn: No more diagnostics found', output[0])
+
+ # :[count]LspDiag prev
+ cursor(7, 2)
+ :4LspDiag prev
+ assert_equal([3, 14], [line('.'), col('.')])
+ output = execute(':4LspDiag prev')->split("\n")
+ assert_equal('Warn: No more diagnostics found', output[0])
+
+ :%d
+ setline(1, ['void blueFunc()', '{', '}'])
+ g:WaitForDiags(0)
+ output = execute('LspDiag show')->split("\n")
+ assert_match('Warn: No diagnostic messages found for', output[0])
+ g:LspOptionsSet({showDiagInPopup: true})
+
+ popup_clear()
+ :%bw!
+enddef
+
+# Test for LSP diagnostics handler
+def g:Test_LspProcessDiagHandler()
+ g:LSPTest_modifyDiags = true
+ g:LspOptionsSet({showDiagInPopup: false})
+
+ :silent! edit XLspProcessDiag.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void blueFunc()
+ {
+ int count, j:
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(1)
+ :redraw!
+ normal gg
+
+ :LspDiag first
+ assert_equal([3, 14], [line('.'), col('.')])
+
+ var output = execute('LspDiag current')->split("\n")
+ assert_equal("this is overridden", output[0])
+
+ g:LspOptionsSet({showDiagInPopup: true})
+ g:LSPTest_modifyDiags = false
+ :%bw!
+enddef
+
+# Diag location list should be automatically updated when the list of diags
+# changes.
+def g:Test_DiagLocListAutoUpdate()
+ :silent! edit XdiagLocListAutoUpdate.c
+ :sleep 200m
+ setloclist(0, [], 'f')
+ var lines: list<string> =<< trim END
+ int i:
+ int j;
+ END
+ setline(1, lines)
+ var bnr = bufnr()
+ g:WaitForServerFileLoad(1)
+ :redraw!
+ var d = lsp#diag#GetDiagsForBuf()[0]
+ assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
+ d.range)
+
+ :LspDiag show
+ assert_equal(1, line('$'))
+ wincmd w
+ setline(2, 'int j:')
+ redraw!
+ g:WaitForDiags(2)
+ var l = lsp#diag#GetDiagsForBuf()
+ assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
+ l[0].range)
+ assert_equal({start: {line: 1, character: 5}, end: {line: 1, character: 6}},
+ l[1].range)
+ wincmd w
+ assert_equal(2, line('$'))
+ wincmd w
+ deletebufline('', 1, '$')
+ redraw!
+ g:WaitForDiags(0)
+ assert_equal([], lsp#diag#GetDiagsForBuf())
+ wincmd w
+ assert_equal([''], getline(1, '$'))
+ :lclose
+
+ setloclist(0, [], 'f')
+ :%bw!
+enddef
+
+# Test that the client have been able to configure the server to speak utf-32
+def g:Test_UnicodeColumnCalc()
+ :silent! edit XUnicodeColumn.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ int count;
+ int fn(int a)
+ {
+ int 😊😊😊😊;
+ 😊😊😊😊 = a;
+
+ int b;
+ b = a;
+ return count + 1;
+ }
+ END
+ setline(1, lines)
+ :redraw!
+
+ cursor(5, 1) # 😊😊😊😊 = a;
+ search('a')
+ assert_equal([],
+ execute('LspGotoDefinition')->split("\n"))
+ assert_equal([2, 12], [line('.'), col('.')])
+
+ cursor(8, 1) # b = a;
+ search('a')
+ assert_equal([],
+ execute('LspGotoDefinition')->split("\n"))
+ assert_equal([2, 12], [line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# Test for multiple LSP diagnostics on the same line
+def g:Test_LspDiag_Multi()
+ :silent! edit XLspDiagMulti.c
+ sleep 200m
+
+ var bnr: number = bufnr()
+
+ var lines =<< trim END
+ int i = "a";
+ int j = i;
+ int y = 0;
+ END
+ setline(1, lines)
+ :redraw!
+ # TODO: Waiting count doesn't include Warning, Info, and Hint diags
+ if clangdVerMajor > 14
+ g:WaitForServerFileLoad(3)
+ else
+ g:WaitForServerFileLoad(2)
+ endif
+ :LspDiag show
+ var qfl: list<dict<any>> = getloclist(0)
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ assert_equal(bnr, qfl[0].bufnr)
+ assert_equal(3, qfl->len())
+ if clangdVerMajor > 14
+ assert_equal([1, 5, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
+ else
+ assert_equal([1, 5, 'W'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
+ endif
+ assert_equal([1, 9, 'E'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
+ assert_equal([2, 9, 'E'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
+ close
+
+ :sleep 100m
+ cursor(2, 1)
+ assert_equal('', execute('LspDiag prev'))
+ assert_equal([1, 9], [line('.'), col('.')])
+
+ assert_equal('', execute('LspDiag prev'))
+ assert_equal([1, 5], [line('.'), col('.')])
+
+ var output = execute('LspDiag prev')->split("\n")
+ assert_equal('Warn: No more diagnostics found', output[0])
+
+ assert_equal('', execute('LspDiag prevWrap'))
+ assert_equal([2, 9], [line('.'), col('.')])
+
+ cursor(2, 1)
+ assert_equal('', execute('LspDiag first'))
+ assert_equal([1, 5], [line('.'), col('.')])
+ assert_equal('', execute('LspDiag next'))
+ assert_equal([1, 9], [line('.'), col('.')])
+ cursor(1, 1)
+ assert_equal('', execute('LspDiag last'))
+ assert_equal([2, 9], [line('.'), col('.')])
+ assert_equal('', execute('LspDiag nextWrap'))
+ assert_equal([1, 5], [line('.'), col('.')])
+ assert_equal('', execute('LspDiag nextWrap'))
+ assert_equal([1, 9], [line('.'), col('.')])
+ popup_clear()
+
+ # Test for :LspDiag here on a line with multiple diagnostics
+ cursor(1, 1)
+ :LspDiag here
+ assert_equal([1, 5], [line('.'), col('.')])
+ var ids = popup_list()
+ assert_equal(1, ids->len())
+ assert_match('Incompatible pointer to integer', getbufline(ids[0]->winbufnr(), 1, '$')[0])
+ popup_clear()
+ cursor(1, 6)
+ :LspDiag here
+ assert_equal([1, 9], [line('.'), col('.')])
+ ids = popup_list()
+ assert_equal(1, ids->len())
+ assert_match('Initializer element is not', getbufline(ids[0]->winbufnr(), 1, '$')[0])
+ popup_clear()
+
+ # Line without diagnostics
+ cursor(3, 1)
+ output = execute('LspDiag here')->split("\n")
+ assert_equal('Warn: No more diagnostics found on this line', output[0])
+
+ g:LspOptionsSet({showDiagInPopup: false})
+ for i in range(1, 5)
+ cursor(1, i)
+ output = execute('LspDiag current')->split('\n')
+ assert_match('Incompatible pointer to integer', output[0])
+ endfor
+ for i in range(6, 12)
+ cursor(1, i)
+ output = execute('LspDiag current')->split('\n')
+ assert_match('Initializer element is not ', output[0])
+ endfor
+ g:LspOptionsSet({showDiagInPopup: true})
+
+ # Check for exact diag ":LspDiag current!"
+ g:LspOptionsSet({showDiagInPopup: false})
+ for i in range(1, 4)
+ cursor(1, i)
+ output = execute('LspDiag! current')->split('\n')
+ assert_equal('Warn: No diagnostic messages found for current position', output[0])
+ endfor
+
+ cursor(1, 5)
+ output = execute('LspDiag! current')->split('\n')
+ assert_match('Incompatible pointer to integer', output[0])
+
+ for i in range(6, 8)
+ cursor(1, i)
+ output = execute('LspDiag! current')->split('\n')
+ assert_equal('Warn: No diagnostic messages found for current position', output[0])
+ endfor
+
+ for i in range(9, 11)
+ cursor(1, i)
+ output = execute('LspDiag! current')->split('\n')
+ assert_match('Initializer element is not ', output[0])
+ endfor
+ for i in range(12, 12)
+ cursor(1, i)
+ output = execute('LspDiag! current')->split('\n')
+ assert_equal('Warn: No diagnostic messages found for current position', output[0])
+ endfor
+
+ g:LspOptionsSet({showDiagInPopup: true})
+
+ # :[count]LspDiag next
+ g:LspOptionsSet({showDiagInPopup: false})
+ cursor(1, 1)
+ :2LspDiag next
+ assert_equal([1, 9], [line('.'), col('.')])
+ :2LspDiag next
+ assert_equal([2, 9], [line('.'), col('.')])
+ output = execute(':2LspDiag next')->split("\n")
+ assert_equal('Warn: No more diagnostics found', output[0])
+
+ cursor(1, 1)
+ :99LspDiag next
+ assert_equal([2, 9], [line('.'), col('.')])
+ g:LspOptionsSet({showDiagInPopup: true})
+
+ # :[count]LspDiag prev
+ g:LspOptionsSet({showDiagInPopup: false})
+ cursor(1, 1)
+ :2LspDiag prev
+ assert_equal('Warn: No more diagnostics found', output[0])
+ cursor(3, 3)
+ :2LspDiag prev
+ assert_equal([1, 9], [line('.'), col('.')])
+ :2LspDiag prev
+ assert_equal([1, 5], [line('.'), col('.')])
+ output = execute(':2LspDiag prev')->split("\n")
+ assert_equal('Warn: No more diagnostics found', output[0])
+
+ cursor(3, 3)
+ :99LspDiag prev
+ assert_equal([1, 5], [line('.'), col('.')])
+ g:LspOptionsSet({showDiagInPopup: true})
+
+ :%bw!
+enddef
+
+# Test for highlight diag inline
+def g:Test_LspHighlightDiagInline()
+ :silent! edit XLspHighlightDiag.c
+ sleep 200m
+ setline(1, [
+ 'int main()',
+ '{',
+ ' struct obj obj',
+ '',
+ ' return 1;',
+ '}',
+ ])
+
+ # TODO: Waiting count doesn't include Warning, Info, and Hint diags
+ g:WaitForDiags(2)
+
+ g:LspOptionsSet({highlightDiagInline: true})
+
+ var props = prop_list(1)
+ assert_equal(0, props->len())
+ props = prop_list(2)
+ assert_equal(0, props->len())
+ props = prop_list(3)
+ assert_equal(2, props->len())
+ assert_equal([
+ {'id': 0, 'col': 12, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineInfo', 'length': 3, 'start': 1},
+ {'id': 0, 'col': 16, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 3, 'start': 1}
+ ], props)
+ props = prop_list(4)
+ assert_equal(0, props->len())
+ props = prop_list(5)
+ assert_equal(1, props->len())
+ assert_equal([{'id': 0, 'col': 5, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 6, 'start': 1}], props)
+ props = prop_list(6)
+ assert_equal(0, props->len())
+
+ g:LspOptionsSet({highlightDiagInline: false})
+ props = prop_list(1, {end_lnum: line('$')})
+ assert_equal(0, props->len())
+
+ :%bw!
+enddef
+
+# Test for :LspCodeAction
+def g:Test_LspCodeAction()
+ silent! edit XLspCodeAction.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void testFunc()
+ {
+ int count;
+ count == 20;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ cursor(4, 1)
+ redraw!
+ :LspCodeAction 1
+ assert_equal("\tcount = 20;", getline(4))
+
+ setline(4, "\tcount = 20:")
+ cursor(4, 1)
+ sleep 500m
+ :LspCodeAction 0
+ assert_equal("\tcount = 20:", getline(4))
+
+ cursor(4, 1)
+ :LspCodeAction 2
+ assert_equal("\tcount = 20:", getline(4))
+
+ cursor(4, 1)
+ :LspCodeAction 1
+ assert_equal("\tcount = 20;", getline(4))
+ bw!
+
+ # pattern and string prefix
+ silent! edit XLspCodeActionPattern.c
+ sleep 200m
+ var lines2: list<string> =<< trim END
+ void testFunc()
+ {
+ int count;
+ if (count = 1) {
+ }
+ }
+ END
+ setline(1, lines2)
+ g:WaitForServerFileLoad(0)
+ cursor(4, 1)
+ redraw!
+ :LspCodeAction use
+ assert_equal("\tif (count == 1) {", getline(4))
+
+ setline(4, "\tif (count = 1) {")
+ cursor(4, 1)
+ sleep 500m
+ :LspCodeAction /paren
+ assert_equal("\tif ((count = 1)) {", getline(4))
+
+ setline(4, "\tif (count = 1) {")
+ cursor(4, 1)
+ sleep 500m
+ :LspCodeAction NON_EXISTING_PREFIX
+ assert_equal("\tif (count = 1) {", getline(4))
+
+ cursor(4, 1)
+ :LspCodeAction /NON_EXISTING_REGEX
+ assert_equal("\tif (count = 1) {", getline(4))
+ bw!
+
+ # empty file
+ assert_equal('', execute('LspCodeAction'))
+
+ # file without an LSP server
+ edit a.raku
+ assert_equal('Error: Language server for "raku" file type supporting "codeAction" feature is not found',
+ execute('LspCodeAction')->split("\n")[0])
+
+ :%bw!
+enddef
+
+# Test for :LspRename
+def g:Test_LspRename()
+ silent! edit XLspRename.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void F1(int count)
+ {
+ count = 20;
+
+ ++count;
+ }
+
+ void F2(int count)
+ {
+ count = 5;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ cursor(1, 1)
+ search('count')
+ redraw!
+ feedkeys(":LspRename\<CR>er\<CR>", "xt")
+ redraw!
+ var expected: list<string> =<< trim END
+ void F1(int counter)
+ {
+ counter = 20;
+
+ ++counter;
+ }
+
+ void F2(int count)
+ {
+ count = 5;
+ }
+ END
+ assert_equal(expected, getline(1, '$'))
+
+ cursor(1, 1)
+ search('counter')
+ LspRename countvar
+ var expected2: list<string> =<< trim END
+ void F1(int countvar)
+ {
+ countvar = 20;
+
+ ++countvar;
+ }
+
+ void F2(int count)
+ {
+ count = 5;
+ }
+ END
+ assert_equal(expected2, getline(1, '$'))
+ sleep 100m
+ bw!
+
+ # empty file
+ assert_equal('', execute('LspRename'))
+
+ # file without an LSP server
+ edit a.raku
+ assert_equal('Error: Language server for "raku" file type supporting "rename" feature is not found',
+ execute('LspRename')->split("\n")[0])
+
+ :%bw!
+enddef
+
+# Test for :LspSelectionExpand and :LspSelectionShrink
+def g:Test_LspSelection()
+ silent! edit XLspSelection.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void fnSel(int count)
+ {
+ int i;
+ for (i = 0; i < 10; i++) {
+ count++;
+ }
+ count = 20;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ # start a block-wise visual mode, LspSelectionExpand should change this to
+ # a characterwise visual mode.
+ exe "normal! 1G\<C-V>G\"_y"
+ cursor(2, 1)
+ redraw!
+ :LspSelectionExpand
+ redraw!
+ normal! y
+ assert_equal('v', visualmode())
+ assert_equal([2, 8], [line("'<"), line("'>")])
+ # start a linewise visual mode, LspSelectionExpand should change this to
+ # a characterwise visual mode.
+ exe "normal! 3GViB\"_y"
+ cursor(4, 29)
+ redraw!
+ :LspSelectionExpand
+ redraw!
+ normal! y
+ assert_equal('v', visualmode())
+ assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+
+ # Expand the visual selection
+ xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
+ xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
+ cursor(5, 8)
+ normal vley
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleley
+ assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleley
+ assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleley
+ assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleleley
+ assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleleleley
+ assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleleleleley
+ assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+
+ # Shrink the visual selection
+ cursor(5, 8)
+ normal vlsy
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelsy
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelsy
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelsy
+ assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelelsy
+ assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelelelsy
+ assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelelelelsy
+ assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+
+ xunmap le
+ xunmap ls
+ bw!
+
+ # empty file
+ assert_equal('', execute('LspSelectionExpand'))
+
+ # file without an LSP server
+ edit a.raku
+ assert_equal('Error: Language server for "raku" file type supporting "selectionRange" feature is not found',
+ execute('LspSelectionExpand')->split("\n")[0])
+
+ :%bw!
+enddef
+
+# Test for :LspGotoDefinition, :LspGotoDeclaration and :LspGotoImpl
+def g:Test_LspGotoSymbol()
+ settagstack(0, {items: []})
+ silent! edit XLspGotoSymbol.cpp
+ sleep 600m
+ var lines: list<string> =<< trim END
+ class base {
+ public:
+ virtual void print();
+ };
+
+ void base::print()
+ {
+ }
+
+ class derived : public base {
+ public:
+ void print() {}
+ };
+
+ void f1(void)
+ {
+ base *bp;
+ derived d;
+ bp = &d;
+
+ bp->print();
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+
+ cursor(21, 6)
+ :LspGotoDeclaration
+ assert_equal([3, 19], [line('.'), col('.')])
+ exe "normal! \<C-t>"
+ assert_equal([21, 6], [line('.'), col('.')])
+ assert_equal(1, winnr('$'))
+
+ :LspGotoDefinition
+ assert_equal([6, 12], [line('.'), col('.')])
+ exe "normal! \<C-t>"
+ assert_equal([21, 6], [line('.'), col('.')])
+ assert_equal(1, winnr('$'))
+
+ # Command modifiers
+ :topleft LspGotoDefinition
+ assert_equal([6, 12], [line('.'), col('.')])
+ assert_equal([1, 2], [winnr(), winnr('$')])
+ close
+ exe "normal! \<C-t>"
+ assert_equal([21, 6], [line('.'), col('.')])
+
+ :tab LspGotoDefinition
+ assert_equal([6, 12], [line('.'), col('.')])
+ assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')])
+ tabclose
+ exe "normal! \<C-t>"
+ assert_equal([21, 6], [line('.'), col('.')])
+
+ # :LspGotoTypeDef
+ cursor(21, 2)
+ :LspGotoTypeDef
+ assert_equal([1, 7], [line('.'), col('.')])
+ exe "normal! \<C-t>"
+ assert_equal([21, 2], [line('.'), col('.')])
+
+ # :LspGotoImpl
+ cursor(21, 6)
+ :LspGotoImpl
+ assert_equal([12, 11], [line('.'), col('.')])
+ exe "normal! \<C-t>"
+ assert_equal([21, 6], [line('.'), col('.')])
+
+ # FIXME: The following tests are failing in Github CI. Comment out for now.
+ if 0
+ # Error cases
+ :messages clear
+ cursor(11, 5)
+ :LspGotoDeclaration
+ var m = execute('messages')->split("\n")
+ assert_equal('symbol declaration is not found', m[1])
+ :messages clear
+ :LspGotoDefinition
+ m = execute('messages')->split("\n")
+ assert_equal('symbol definition is not found', m[1])
+ :messages clear
+ :LspGotoImpl
+ m = execute('messages')->split("\n")
+ assert_equal('symbol implementation is not found', m[1])
+ :messages clear
+ endif
+
+ # Test for LspPeekDeclaration
+ cursor(21, 6)
+ var bnum = bufnr()
+ :LspPeekDeclaration
+ var plist = popup_list()
+ assert_true(1, plist->len())
+ assert_equal(bnum, plist[0]->winbufnr())
+ assert_equal(3, line('.', plist[0]))
+ popup_clear()
+ # tag stack should not be changed
+ assert_fails("normal! \<C-t>", 'E555:')
+
+ # Test for LspPeekDefinition
+ :LspPeekDefinition
+ plist = popup_list()
+ assert_true(1, plist->len())
+ assert_equal(bnum, plist[0]->winbufnr())
+ assert_equal(6, line('.', plist[0]))
+ popup_clear()
+ # tag stack should not be changed
+ assert_fails("normal! \<C-t>", 'E555:')
+
+ # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14.
+ # This clangd version is not available in Github CI.
+
+ :%bw!
+
+ # empty file
+ assert_equal('', execute('LspGotoDefinition'))
+ assert_equal('', execute('LspGotoDeclaration'))
+ assert_equal('', execute('LspGotoImpl'))
+
+ # file without an LSP server
+ edit a.raku
+ assert_equal('Error: Language server for "raku" file type supporting "definition" feature is not found',
+ execute('LspGotoDefinition')->split("\n")[0])
+ assert_equal('Error: Language server for "raku" file type supporting "declaration" feature is not found',
+ execute('LspGotoDeclaration')->split("\n")[0])
+ assert_equal('Error: Language server for "raku" file type supporting "implementation" feature is not found',
+ execute('LspGotoImpl')->split("\n")[0])
+
+ :%bw!
+enddef
+
+# Test for :LspHighlight
+def g:Test_LspHighlight()
+ silent! edit XLspHighlight.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void f1(int arg)
+ {
+ int i = arg;
+ arg = 2;
+ if (arg == 2) {
+ arg = 3;
+ }
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ cursor(1, 13)
+ :LspHighlight
+ var expected: dict<any>
+ expected = {id: 0, col: 13, end: 1, type: 'LspTextRef', length: 3, start: 1}
+ expected.type_bufnr = 0
+ assert_equal([expected], prop_list(1))
+ expected = {id: 0, col: 11, end: 1, type: 'LspReadRef', length: 3, start: 1}
+ expected.type_bufnr = 0
+ assert_equal([expected], prop_list(3))
+ expected = {id: 0, col: 3, end: 1, type: 'LspWriteRef', length: 3, start: 1}
+ expected.type_bufnr = 0
+ assert_equal([expected], prop_list(4))
+ :LspHighlightClear
+ assert_equal([], prop_list(1))
+ assert_equal([], prop_list(3))
+ assert_equal([], prop_list(4))
+
+ cursor(5, 3) # if (arg == 2) {
+ var output = execute('LspHighlight')->split("\n")
+ assert_equal('Warn: No highlight for the current position', output[0])
+ :%bw!
+enddef
+
+# Test for :LspHover
+def g:Test_LspHover()
+ silent! edit XLspHover.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ int f1(int a)
+ {
+ return 0;
+ }
+
+ void f2(void)
+ {
+ f1(5);
+ char *z = "z";
+ f1(z);
+ }
+ END
+ setline(1, lines)
+ if clangdVerMajor > 14
+ g:WaitForServerFileLoad(1)
+ else
+ g:WaitForServerFileLoad(0)
+ endif
+ cursor(8, 4)
+ var output = execute(':LspHover')->split("\n")
+ assert_equal([], output)
+ var p: list<number> = popup_list()
+ assert_equal(1, p->len())
+ assert_equal(['### function `f1` ', '', '---', '→ `int` ', 'Parameters: ', '- `int a`', '', '---', '```cpp', 'int f1(int a)', '```'], getbufline(winbufnr(p[0]), 1, '$'))
+ popup_close(p[0])
+ cursor(7, 1)
+ output = execute(':LspHover')->split("\n")
+ assert_equal('Warn: No documentation found for current keyword', output[0])
+ output = execute(':silent LspHover')->split("\n")
+ assert_equal([], output)
+ assert_equal([], popup_list())
+
+ # Show current diagnostic as to open another popup.
+ # Then we can test that LspHover closes all existing popups
+ cursor(10, 6)
+ :LspDiag current
+ assert_equal(1, popup_list()->len())
+ :LspHover
+ assert_equal(1, popup_list()->len())
+ popup_clear()
+
+ # Show hover information in a preview window
+ g:LspOptionsSet({hoverInPreview: true})
+ cursor(8, 4)
+ :LspHover
+ assert_equal([2, 2, 'preview'], [winnr('$'), winnr(), win_gettype(1)])
+ assert_equal('LspHover', winbufnr(1)->bufname())
+ cursor(9, 9)
+ :LspHover
+ assert_equal([2, 2, 'preview'], [winnr('$'), winnr(), win_gettype(1)])
+ g:LspOptionsSet({hoverInPreview: false})
+ :pclose
+
+ :%bw!
+enddef
+
+# Test for :LspShowSignature
+def g:Test_LspShowSignature()
+ silent! edit XLspShowSignature.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ int MyFunc(int a, int b)
+ {
+ return 0;
+ }
+
+ void f2(void)
+ {
+ MyFunc(
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(2)
+ cursor(8, 10)
+ :LspShowSignature
+ var p: list<number> = popup_list()
+ var bnr: number = winbufnr(p[0])
+ assert_equal(1, p->len())
+ assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
+ var expected: dict<any>
+ expected = {id: 0, col: 8, end: 1, type: 'signature', length: 5, start: 1}
+ expected.type_bufnr = bnr
+ assert_equal([expected], prop_list(1, {bufnr: bnr}))
+ popup_close(p[0])
+
+ setline(line('.'), ' MyFunc(10, ')
+ cursor(8, 13)
+ :LspShowSignature
+ p = popup_list()
+ bnr = winbufnr(p[0])
+ assert_equal(1, p->len())
+ assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
+ expected = {id: 0, col: 15, end: 1, type: 'signature', length: 5, start: 1}
+ expected.type_bufnr = bnr
+ assert_equal([expected], prop_list(1, {bufnr: bnr}))
+ popup_close(p[0])
+ :%bw!
+enddef
+
+# Test for :LspSymbolSearch
+def g:Test_LspSymbolSearch()
+ silent! edit XLspSymbolSearch.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void lsptest_funcA()
+ {
+ }
+
+ void lsptest_funcB()
+ {
+ }
+
+ void lsptest_funcC()
+ {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch lsptest_funcB\<CR>", "xt")
+ assert_equal([5, 6], [line('.'), col('.')])
+
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt")
+ assert_equal([9, 6], [line('.'), col('.')])
+
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch lsptest_func\<CR>A\<BS>B\<CR>", "xt")
+ assert_equal([5, 6], [line('.'), col('.')])
+
+ var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")
+ assert_equal('Warn: Symbol "lsptest_nonexist" is not found', output[0])
+
+ :%bw!
+enddef
+
+# Test for :LspIncomingCalls
+def g:Test_LspIncomingCalls()
+ silent! edit XLspIncomingCalls.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void xFuncIncoming(void)
+ {
+ }
+
+ void aFuncIncoming(void)
+ {
+ xFuncIncoming();
+ }
+
+ void bFuncIncoming(void)
+ {
+ xFuncIncoming();
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ cursor(1, 6)
+ :LspIncomingCalls
+ assert_equal([1, 2], [winnr(), winnr('$')])
+ var l = getline(1, '$')
+ assert_equal('# Incoming calls to "xFuncIncoming"', l[0])
+ assert_match('- xFuncIncoming (XLspIncomingCalls.c \[.*\])', l[1])
+ assert_match(' + aFuncIncoming (XLspIncomingCalls.c \[.*\])', l[2])
+ assert_match(' + bFuncIncoming (XLspIncomingCalls.c \[.*\])', l[3])
+ :%bw!
+enddef
+
+# Test for :LspOutline
+def g:Test_LspOutline()
+ silent! edit XLspOutline.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void aFuncOutline(void)
+ {
+ }
+
+ void bFuncOutline(void)
+ {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ var winid = win_getid()
+ :LspOutline
+ assert_equal(2, winnr('$'))
+ var bnum = winbufnr(winid + 1)
+ assert_equal('LSP-Outline', bufname(bnum))
+ assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
+
+ # Validate position vert topleft
+ assert_equal(['row', [['leaf', winid + 1], ['leaf', winid]]], winlayout())
+
+ # Validate default width is 20
+ assert_equal(20, winwidth(winid + 1))
+
+ execute $':{bnum}bw'
+
+ # Validate position vert botright
+ g:LspOptionsSet({outlineOnRight: true})
+ :LspOutline
+ assert_equal(2, winnr('$'))
+ bnum = winbufnr(winid + 2)
+ assert_equal('LSP-Outline', bufname(bnum))
+ assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
+ assert_equal(['row', [['leaf', winid], ['leaf', winid + 2]]], winlayout())
+ g:LspOptionsSet({outlineOnRight: false})
+ execute $':{bnum}bw'
+
+ # Validate <mods> position botright (below)
+ :botright LspOutline
+ assert_equal(2, winnr('$'))
+ bnum = winbufnr(winid + 3)
+ assert_equal('LSP-Outline', bufname(bnum))
+ assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
+ assert_equal(['col', [['leaf', winid], ['leaf', winid + 3]]], winlayout())
+ execute $':{bnum}bw'
+
+ # Validate that outlineWinSize works for LspOutline
+ g:LspOptionsSet({outlineWinSize: 40})
+ :LspOutline
+ assert_equal(2, winnr('$'))
+ bnum = winbufnr(winid + 4)
+ assert_equal('LSP-Outline', bufname(bnum))
+ assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
+ assert_equal(40, winwidth(winid + 4))
+ execute $':{bnum}bw'
+ g:LspOptionsSet({outlineWinSize: 20})
+
+ # Validate that <count> works for LspOutline
+ :37LspOutline
+ assert_equal(2, winnr('$'))
+ bnum = winbufnr(winid + 5)
+ assert_equal('LSP-Outline', bufname(bnum))
+ assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
+ assert_equal(37, winwidth(winid + 5))
+ execute $':{bnum}bw'
+
+ :%bw!
+enddef
+
+# Test for setting the 'tagfunc'
+def g:Test_LspTagFunc()
+ var lines: list<string> =<< trim END
+ void aFuncTag(void)
+ {
+ xFuncTag();
+ }
+
+ void bFuncTag(void)
+ {
+ xFuncTag();
+ }
+
+ void xFuncTag(void)
+ {
+ }
+ END
+ writefile(lines, 'Xtagfunc.c')
+ :silent! edit Xtagfunc.c
+ g:WaitForServerFileLoad(1)
+ :setlocal tagfunc=lsp#lsp#TagFunc
+ cursor(3, 4)
+ :exe "normal \<C-]>"
+ assert_equal([11, 6], [line('.'), col('.')])
+ cursor(1, 1)
+ assert_fails('exe "normal \<C-]>"', 'E433:')
+
+ :set tagfunc&
+ :%bw!
+ delete('Xtagfunc.c')
+enddef
+
+# Test for the LspDiagsUpdated autocmd
+def g:Test_LspDiagsUpdated_Autocmd()
+ g:LspAutoCmd = 0
+ autocmd_add([{event: 'User', pattern: 'LspDiagsUpdated', cmd: 'g:LspAutoCmd = g:LspAutoCmd + 1'}])
+ silent! edit XLspDiagsAutocmd.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void aFuncDiag(void)
+ {
+ return;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ setline(3, ' return:')
+ redraw!
+ g:WaitForDiags(1)
+ setline(3, ' return;')
+ redraw!
+ g:WaitForDiags(0)
+ :%bw!
+ autocmd_delete([{event: 'User', pattern: 'LspDiagsUpdated'}])
+ assert_equal(5, g:LspAutoCmd)
+enddef
+
+# Test custom notification handlers
+def g:Test_LspCustomNotificationHandlers()
+
+ g:LSPTest_customNotificationHandlerReplied = false
+
+ silent! edit XcustomNotification.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ int a = 1;
+ int main(void) {
+ return a;
+ }
+ END
+ setline(1, lines)
+ g:WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied))
+ :%bw!
+enddef
+
+def g:Test_ScanFindIdent()
+ :silent! edit XscanFindIdent.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ int countFI;
+ int fnFI(int a)
+ {
+ int hello;
+ hello = a;
+ return countFI + 1;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ :redraw!
+
+ # LspGotoDefinition et al
+ cursor(5, 10)
+ assert_equal([], execute('LspGotoDefinition')->split("\n"))
+ assert_equal([2, 14], [line('.'), col('.')])
+
+ cursor(6, 10)
+ assert_equal([], execute('LspGotoDefinition')->split("\n"))
+ assert_equal([1, 5], [line('.'), col('.')])
+
+ # LspShowReferences
+ cursor(6, 10)
+ assert_equal([], execute('LspShowReferences')->split("\n"))
+ :lclose
+
+ # LspRename
+ cursor(6, 10)
+ assert_equal([], execute('LspRename counterFI')->split("\n"))
+ sleep 100m
+ assert_equal('int counterFI;', getline(1))
+ assert_equal(' return counterFI + 1;', getline(6))
+
+ :%bw!
+enddef
+
+# Test for doing omni completion from the first column
+def g:Test_OmniComplete_FirstColumn()
+ :silent! edit XOmniCompleteFirstColumn.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ typedef struct Foo_ {
+ } Foo_t;
+
+ #define FOO 1
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ feedkeys("G0i\<C-X>\<C-O>", 'xt')
+ assert_equal('Foo_t#define FOO 1', getline('.'))
+ :%bw!
+enddef
+
+# Test for doing omni completion with a multibyte character
+def g:Test_OmniComplete_Multibyte()
+ :silent! edit XOmniCompleteMultibyte.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <string.h>
+ void Fn(void)
+ {
+ int thisVar = 1;
+ int len = strlen("©©©©©") + thisVar;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(5, 36)
+ feedkeys("cwthis\<C-X>\<C-O>", 'xt')
+ assert_equal(' int len = strlen("©©©©©") + thisVar;', getline('.'))
+ :%bw!
+enddef
+
+# Test for doing omni completion for a struct field
+def g:Test_OmniComplete_Struct()
+ :silent! edit XOmniCompleteStruct.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ struct test_ {
+ int foo;
+ int bar;
+ int baz;
+ };
+ void Fn(void)
+ {
+ struct test_ myTest;
+ struct test_ *pTest;
+ myTest.bar = 10;
+ pTest->bar = 20;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(10, 12)
+ feedkeys("cwb\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt')
+ assert_equal(' myTest.baz = 10;', getline('.'))
+ cursor(11, 12)
+ feedkeys("cw\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt')
+ assert_equal(' pTest->baz = 20;', getline('.'))
+ :%bw!
+enddef
+
+# Test for doing omni completion after an opening parenthesis.
+# This used to result in an error message.
+def g:Test_OmniComplete_AfterParen()
+ :silent! edit XOmniCompleteAfterParen.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <stdio.h>
+ void Fn(void)
+ {
+ printf(
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(2)
+ redraw!
+
+ cursor(4, 1)
+ feedkeys("A\<C-X>\<C-O>\<C-Y>", 'xt')
+ assert_equal(' printf(', getline('.'))
+ :%bw!
+enddef
+
+# Test for inlay hints
+def g:Test_InlayHints()
+ :silent! edit XinlayHints.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void func1(int a, int b)
+ {
+ }
+
+ void func2()
+ {
+ func1(10, 20);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ assert_equal([], prop_list(7))
+
+ :LspInlayHints enable
+ var p = prop_list(7)
+ assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type])
+ assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type])
+
+ :LspInlayHints disable
+ assert_equal([], prop_list(7))
+
+ g:LspOptionsSet({showInlayHints: true})
+ assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type])
+ assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type])
+
+ g:LspOptionsSet({showInlayHints: false})
+ assert_equal([], prop_list(7))
+
+ :hide enew
+ :LspInlayHints enable
+ :bprev
+ assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type])
+ assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type])
+
+ :hide enew
+ :LspInlayHints disable
+ :bprev
+ assert_equal([], prop_list(7))
+
+ :%bw!
+enddef
+
+# Test for reloading a modified buffer with diags
+def g:Test_ReloadBufferWithDiags()
+ var lines: list<string> =<< trim END
+ void ReloadBufferFunc1(void)
+ {
+ int a:
+ }
+ END
+ writefile(lines, 'Xreloadbuffer.c')
+ :silent! edit Xreloadbuffer.c
+ g:WaitForServerFileLoad(1)
+ var signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal(3, signs[0].lnum)
+ append(0, ['', ''])
+ signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal(5, signs[0].lnum)
+ :edit!
+ sleep 200m
+ signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal(3, signs[0].lnum)
+
+ :%bw!
+ delete('Xreloadbuffer.c')
+enddef
+
+# Test for ":LspDiag" sub commands
+def g:Test_LspDiagsSubcmd()
+ new XLspDiagsSubCmd.raku
+
+ feedkeys(":LspDiag \<C-A>\<CR>", 'xt')
+ assert_equal('LspDiag first current here highlight last next nextWrap prev prevWrap show', @:)
+ feedkeys(":LspDiag highlight \<C-A>\<CR>", 'xt')
+ assert_equal('LspDiag highlight enable disable', @:)
+ assert_equal(['Error: :LspDiag - Unsupported argument "xyz"'],
+ execute('LspDiag xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag - Unsupported argument "first xyz"'],
+ execute('LspDiag first xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag - Unsupported argument "current xyz"'],
+ execute('LspDiag current xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag - Unsupported argument "here xyz"'],
+ execute('LspDiag here xyz')->split("\n"))
+ assert_equal(['Error: Argument required for ":LspDiag highlight"'],
+ execute('LspDiag highlight')->split("\n"))
+ assert_equal(['Error: :LspDiag highlight - Unsupported argument "xyz"'],
+ execute('LspDiag highlight xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag highlight - Unsupported argument "enable xyz"'],
+ execute('LspDiag highlight enable xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag - Unsupported argument "last xyz"'],
+ execute('LspDiag last xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag - Unsupported argument "next xyz"'],
+ execute('LspDiag next xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag - Unsupported argument "prev xyz"'],
+ execute('LspDiag prev xyz')->split("\n"))
+ assert_equal(['Error: :LspDiag - Unsupported argument "show xyz"'],
+ execute('LspDiag show xyz')->split("\n"))
+
+ :%bw!
+enddef
+
+# Test for the :LspServer command.
+def g:Test_LspServer()
+ new a.raku
+ assert_equal(['Warn: No Lsp servers found for "a.raku"'],
+ execute('LspServer debug on')->split("\n"))
+ assert_equal(['Warn: No Lsp servers found for "a.raku"'],
+ execute('LspServer restart')->split("\n"))
+ assert_equal(['Warn: No Lsp servers found for "a.raku"'],
+ execute('LspServer show status')->split("\n"))
+ assert_equal(['Warn: No Lsp servers found for "a.raku"'],
+ execute('LspServer trace verbose')->split("\n"))
+ assert_equal(['Error: LspServer - Unsupported argument "xyz"'],
+ execute('LspServer xyz')->split("\n"))
+ assert_equal(['Error: Argument required for ":LspServer debug"'],
+ execute('LspServer debug')->split("\n"))
+ assert_equal(['Error: Unsupported argument "xyz"'],
+ execute('LspServer debug xyz')->split("\n"))
+ assert_equal(['Error: Unsupported argument "on xyz"'],
+ execute('LspServer debug on xyz')->split("\n"))
+ assert_equal(['Error: Argument required for ":LspServer show"'],
+ execute('LspServer show')->split("\n"))
+ assert_equal(['Error: Unsupported argument "xyz"'],
+ execute('LspServer show xyz')->split("\n"))
+ assert_equal(['Error: Unsupported argument "status xyz"'],
+ execute('LspServer show status xyz')->split("\n"))
+ assert_equal(['Error: Argument required for ":LspServer trace"'],
+ execute('LspServer trace')->split("\n"))
+ assert_equal(['Error: Unsupported argument "xyz"'],
+ execute('LspServer trace xyz')->split("\n"))
+ assert_equal(['Error: Unsupported argument "verbose xyz"'],
+ execute('LspServer trace verbose xyz')->split("\n"))
+ :%bw!
+enddef
+
+# Test for the diagnostics virtual text text property
+def g:Test_DiagVirtualText()
+ if !has('patch-9.0.1157')
+ # Doesn't support virtual text
+ return
+ endif
+ g:LspOptionsSet({highlightDiagInline: false})
+ :silent! edit XdiagVirtualText.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void DiagVirtualTextFunc1()
+ {
+ int i:
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(1)
+ redraw!
+
+ var p = prop_list(1, {end_lnum: line('$')})
+ assert_equal(0, p->len())
+
+ g:LspOptionsSet({showDiagWithVirtualText: true})
+ p = prop_list(1, {end_lnum: line('$')})
+ assert_equal(1, p->len())
+ assert_equal([3, 'LspDiagVirtualTextError'], [p[0].lnum, p[0].type])
+
+ g:LspOptionsSet({showDiagWithVirtualText: false})
+ p = prop_list(1, {end_lnum: line('$')})
+ assert_equal(0, p->len())
+
+ g:LspOptionsSet({highlightDiagInline: true})
+ :%bw!
+enddef
+
+# Test for enabling and disabling the "showDiagWithSign" option.
+def g:Test_DiagSigns()
+ :silent! edit Xdiagsigns.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void DiagSignsFunc1(void)
+ {
+ int a:
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(1)
+ redraw!
+
+ var signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal([1, 3], [signs->len(), signs[0].lnum])
+
+ g:LspOptionsSet({showDiagWithSign: false})
+ signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal([], signs)
+ g:LspOptionsSet({showDiagWithSign: true})
+ signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal([1, 3], [signs->len(), signs[0].lnum])
+
+ # Test for enabling/disabling "autoHighlightDiags"
+ g:LspOptionsSet({autoHighlightDiags: false})
+ signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal([], signs)
+ g:LspOptionsSet({autoHighlightDiags: true})
+ signs = sign_getplaced('%', {group: '*'})[0].signs
+ assert_equal([1, 3], [signs->len(), signs[0].lnum])
+
+ :%bw!
+enddef
+
+# TODO:
+# 1. Add a test for autocompletion with a single match while ignoring case.
+# After the full matched name is typed, the completion popup should still
+# be displayed. e.g.
+#
+# int MyVar = 1;
+# int abc = myvar<C-N><C-Y>
+# 2. Add a test for jumping to a non-existing symbol definition, declaration.
+
+# Start the C language server. Returns true on success and false on failure.
+def g:StartLangServer(): bool
+ return g:StartLangServerWithFile('Xtest.c')
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/common.vim b/vim/pack/downloads/opt/lsp/test/common.vim
new file mode 100644
index 0000000..eeb852c
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/common.vim
@@ -0,0 +1,166 @@
+vim9script
+# Common routines used for running the unit tests
+
+# Load the LSP plugin. Also enable syntax, file type detection.
+def g:LoadLspPlugin()
+ syntax on
+ filetype on
+ filetype plugin on
+ filetype indent on
+
+ # Set the $LSP_PROFILE environment variable to profile the LSP plugin
+ var do_profile: bool = false
+ if exists('$LSP_PROFILE')
+ do_profile = true
+ endif
+
+ if do_profile
+ # profile the LSP plugin
+ profile start lsp_profile.txt
+ profile! file */lsp/*
+ endif
+
+ g:LSPTest = true
+ source ../plugin/lsp.vim
+enddef
+
+# The WaitFor*() functions are reused from the Vim test suite.
+#
+# Wait for up to five seconds for "assert" to return zero. "assert" must be a
+# (lambda) function containing one assert function. Example:
+# call WaitForAssert({-> assert_equal("dead", job_status(job)})
+#
+# A second argument can be used to specify a different timeout in msec.
+#
+# Return zero for success, one for failure (like the assert function).
+func g:WaitForAssert(assert, ...)
+ let timeout = get(a:000, 0, 5000)
+ if g:WaitForCommon(v:null, a:assert, timeout) < 0
+ return 1
+ endif
+ return 0
+endfunc
+
+# Either "expr" or "assert" is not v:null
+# Return the waiting time for success, -1 for failure.
+func g:WaitForCommon(expr, assert, timeout)
+ " using reltime() is more accurate, but not always available
+ let slept = 0
+ if exists('*reltimefloat')
+ let start = reltime()
+ endif
+
+ while 1
+ if type(a:expr) == v:t_func
+ let success = a:expr()
+ elseif type(a:assert) == v:t_func
+ let success = a:assert() == 0
+ else
+ let success = eval(a:expr)
+ endif
+ if success
+ return slept
+ endif
+
+ if slept >= a:timeout
+ break
+ endif
+ if type(a:assert) == v:t_func
+ " Remove the error added by the assert function.
+ call remove(v:errors, -1)
+ endif
+
+ sleep 10m
+ if exists('*reltimefloat')
+ let slept = float2nr(reltimefloat(reltime(start)) * 1000)
+ else
+ let slept += 10
+ endif
+ endwhile
+
+ return -1 " timed out
+endfunc
+
+# Wait for up to five seconds for "expr" to become true. "expr" can be a
+# stringified expression to evaluate, or a funcref without arguments.
+# Using a lambda works best. Example:
+# call WaitFor({-> status == "ok"})
+#
+# A second argument can be used to specify a different timeout in msec.
+#
+# When successful the time slept is returned.
+# When running into the timeout an exception is thrown, thus the function does
+# not return.
+func g:WaitFor(expr, ...)
+ let timeout = get(a:000, 0, 5000)
+ let slept = g:WaitForCommon(a:expr, v:null, timeout)
+ if slept < 0
+ throw 'WaitFor() timed out after ' .. timeout .. ' msec'
+ endif
+ return slept
+endfunc
+
+# Wait for diagnostic messages from the LSP server.
+# Waits for a maximum of (150 * 200) / 1000 = 30 seconds
+def g:WaitForDiags(errCount: number)
+ var retries = 0
+ while retries < 200
+ var d = lsp#lsp#ErrorCount()
+ if d.Error == errCount
+ break
+ endif
+ retries += 1
+ :sleep 150m
+ endwhile
+
+ assert_equal(errCount, lsp#lsp#ErrorCount().Error)
+ if lsp#lsp#ErrorCount().Error != errCount
+ :LspDiag show
+ assert_report(getloclist(0)->string())
+ :lclose
+ endif
+enddef
+
+# Wait for the LSP server to load and process a file. This works by waiting
+# for a certain number of diagnostic messages from the server.
+def g:WaitForServerFileLoad(diagCount: number)
+ :redraw!
+ var waitCount = diagCount
+ if waitCount == 0
+ # Introduce a temporary diagnostic
+ append('$', '-')
+ redraw!
+ waitCount = 1
+ endif
+ g:WaitForDiags(waitCount)
+ if waitCount != diagCount
+ # Remove the temporary line
+ deletebufline('%', '$')
+ redraw!
+ g:WaitForDiags(0)
+ endif
+enddef
+
+# Start the language server. Returns true on success and false on failure.
+# 'fname' is the name of a dummy file to start the server.
+def g:StartLangServerWithFile(fname: string): bool
+ # Edit a dummy file to start the LSP server
+ exe ':silent! edit ' .. fname
+ # Wait for the LSP server to become ready (max 10 seconds)
+ var maxcount = 100
+ while maxcount > 0 && !g:LspServerReady()
+ :sleep 100m
+ maxcount -= 1
+ endwhile
+ var serverStatus: bool = g:LspServerReady()
+ :bw!
+
+ if !serverStatus
+ writefile(['FAIL: Not able to start the language server'], 'results.txt')
+ qall!
+ endif
+
+ return serverStatus
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump
new file mode 100644
index 0000000..175c1dd
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump
@@ -0,0 +1,10 @@
+|H+0#00e0003#ffffff0|>|c+0#af5f00255&|o|n|s|t| +0#0000000&|h|t@1|p| |=| |r|e|q|u|i|r|e|(|'+0#e000002&|h|t@1|p|'|)+0#0000000&| @44
+| +0#0000e05#a8a8a8255@1|h+0#0000000#ffffff0|t@1|p|.|c|r|e> @64
+|~+0#4040ff13&| @4| +0#0000001#ffd7ff255|c|r|e|a|t|e|S|e|r|v|e|r| |f| | +0#4040ff13#ffffff0@52
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump
new file mode 100644
index 0000000..3b6ecab
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump
@@ -0,0 +1,10 @@
+|H+0#00e0003#ffffff0|>|c+0#af5f00255&|o|n|s|t| +0#0000000&|h|t@1|p| |=| |r|e|q|u|i|r|e|(|'+0#e000002&|h|t@1|p|'|)+0#0000000&| @44
+| +0#0000e05#a8a8a8255@1|h+0#0000000#ffffff0|t@1|p|.|c|r> @65
+|~+0#4040ff13&| @4| +0#0000001#ffd7ff255|c|r|e|a|t|e|S|e|r|v|e|r| |f| | +0#4040ff13#ffffff0@52
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/vim/pack/downloads/opt/lsp/test/gopls_tests.vim b/vim/pack/downloads/opt/lsp/test/gopls_tests.vim
new file mode 100644
index 0000000..bf00c89
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/gopls_tests.vim
@@ -0,0 +1,121 @@
+vim9script
+# Unit tests for Vim Language Server Protocol (LSP) golang client
+
+source common.vim
+
+var lspServers = [{
+ filetype: ['go'],
+ path: exepath('gopls'),
+ args: ['serve']
+ }]
+call LspAddServer(lspServers)
+echomsg systemlist($'{lspServers[0].path} version')
+
+# Test for :LspGotoDefinition, :LspGotoDeclaration, etc.
+# This test also tests that multiple locations will be
+# shown in a list or popup
+def g:Test_LspGoto()
+ :silent! edit Xtest.go
+ var bnr = bufnr()
+
+ sleep 200m
+
+ var lines =<< trim END
+ package main
+
+ type A/*goto implementation*/ interface {
+ Hello()
+ }
+
+ type B struct{}
+
+ func (b *B) Hello() {}
+
+ type C struct{}
+
+ func (c *C) Hello() {}
+
+ func main() {
+ }
+ END
+
+ setline(1, lines)
+ :redraw!
+ g:WaitForServerFileLoad(0)
+
+ cursor(9, 10)
+ :LspGotoDefinition
+ assert_equal([7, 6], [line('.'), col('.')])
+ exe "normal! \<C-t>"
+ assert_equal([9, 10], [line('.'), col('.')])
+
+ cursor(9, 13)
+ :LspGotoImpl
+ assert_equal([4, 9], [line('.'), col('.')])
+
+ cursor(13, 13)
+ :LspGotoImpl
+ assert_equal([4, 9], [line('.'), col('.')])
+
+ # Two implementions needs to be shown in a location list
+ cursor(4, 9)
+ assert_equal('', execute('LspGotoImpl'))
+ sleep 200m
+ var loclist: list<dict<any>> = getloclist(0)
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ assert_equal(2, loclist->len())
+ assert_equal(bnr, loclist[0].bufnr)
+ assert_equal([9, 13, ''], [loclist[0].lnum, loclist[0].col, loclist[0].type])
+ assert_equal([13, 13, ''], [loclist[1].lnum, loclist[1].col, loclist[1].type])
+ lclose
+
+ # Two implementions needs to be shown in a quickfix list
+ g:LspOptionsSet({ useQuickfixForLocations: true })
+ cursor(4, 9)
+ assert_equal('', execute('LspGotoImpl'))
+ sleep 200m
+ var qfl: list<dict<any>> = getqflist()
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ assert_equal(2, qfl->len())
+ assert_equal(bnr, qfl[0].bufnr)
+ assert_equal([9, 13, ''], [qfl[0].lnum, qfl[0].col, qfl[0].type])
+ assert_equal([13, 13, ''], [qfl[1].lnum, qfl[1].col, qfl[1].type])
+ cclose
+ g:LspOptionsSet({ useQuickfixForLocations: false })
+
+ # Two implementions needs to be peeked in a popup
+ cursor(4, 9)
+ :LspPeekImpl
+ sleep 10m
+ var ids = popup_list()
+ assert_equal(2, ids->len())
+ var filePopupAttrs = ids[0]->popup_getoptions()
+ var refPopupAttrs = ids[1]->popup_getoptions()
+ assert_match('Xtest', filePopupAttrs.title)
+ assert_match('Implementation', refPopupAttrs.title)
+ assert_equal(9, line('.', ids[0])) # current line in left panel
+ assert_equal(2, line('$', ids[1])) # last line in right panel
+ feedkeys("j\<CR>", 'xt')
+ assert_equal(13, line('.'))
+ assert_equal([], popup_list())
+ popup_clear()
+
+ # Jump to the first implementation
+ cursor(4, 9)
+ assert_equal('', execute(':1LspGotoImpl'))
+ assert_equal([9, 13], [line('.'), col('.')])
+
+ # Jump to the second implementation
+ cursor(4, 9)
+ assert_equal('', execute(':2LspGotoImpl'))
+ assert_equal([13, 13], [line('.'), col('.')])
+ bw!
+enddef
+
+# Start the gopls language server. Returns true on success and false on
+# failure.
+def g:StartLangServer(): bool
+ return g:StartLangServerWithFile('Xtest.go')
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/markdown_tests.vim b/vim/pack/downloads/opt/lsp/test/markdown_tests.vim
new file mode 100644
index 0000000..59abfb6
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/markdown_tests.vim
@@ -0,0 +1,305 @@
+vim9script
+
+# Unit tests for the Github Flavored Markdown parser
+
+import '../autoload/lsp/markdown.vim' as md
+
+# Test for different markdowns
+def g:Test_Markdown()
+ var tests: list<list<list<any>>> = [
+ [
+ # Different headings
+ # Input text
+ [
+ '# First level heading',
+ '## Second level heading',
+ '### Third level heading',
+ '# Heading with leading and trailing whitespaces ',
+ 'Multiline setext heading ',
+ 'of level 1',
+ '===',
+ 'Multiline setext heading\',
+ 'of level 2',
+ '---'
+ ],
+ # Expected text
+ [
+ 'First level heading',
+ '',
+ 'Second level heading',
+ '',
+ 'Third level heading',
+ '',
+ 'Heading with leading and trailing whitespaces',
+ '',
+ 'Multiline setext heading',
+ 'of level 1',
+ '',
+ 'Multiline setext heading',
+ 'of level 2'
+ ],
+ # Expected text properties
+ [
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 19}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 20}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 19}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 45}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 24}],
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 10}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 24}],
+ [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 10}],
+ ]
+ ],
+ [
+ # Bold text style
+ # Input text
+ [
+ 'This **word** should be bold',
+ '',
+ '**This line should be bold**',
+ '',
+ 'This __word__ should be bold',
+ '',
+ '__This line should be bold__'
+ ],
+ # Expected text
+ [
+ 'This word should be bold',
+ '',
+ 'This line should be bold',
+ '',
+ 'This word should be bold',
+ '',
+ 'This line should be bold'
+ ],
+ # Expected text properties
+ [
+ [{'col': 6, 'type': 'LspMarkdownBold', 'length': 4}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownBold', 'length': 24}],
+ [],
+ [{'col': 6, 'type': 'LspMarkdownBold', 'length': 4}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownBold', 'length': 24}]
+ ]
+ ],
+ [
+ # Italic text style
+ # Input text
+ [
+ 'This *word* should be italic',
+ '',
+ '*This line should be italic*',
+ '',
+ 'This _word_ should be italic',
+ '',
+ '_This line should be italic_'
+ ],
+ # Expected text
+ [
+ 'This word should be italic',
+ '',
+ 'This line should be italic',
+ '',
+ 'This word should be italic',
+ '',
+ 'This line should be italic'
+ ],
+ # Expected text properties
+ [
+ [{'col': 6, 'type': 'LspMarkdownItalic', 'length': 4}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 26}],
+ [],
+ [{'col': 6, 'type': 'LspMarkdownItalic', 'length': 4}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 26}]
+ ],
+ ],
+ [
+ # strikethrough text style
+ # Input text
+ [
+ 'This ~word~ should be strikethrough',
+ '',
+ '~This line should be strikethrough~'
+ ],
+ # Expected text
+ [
+ 'This word should be strikethrough',
+ '',
+ 'This line should be strikethrough'
+ ],
+ # Expected text properties
+ [
+ [{'col': 6, 'type': 'LspMarkdownStrikeThrough', 'length': 4}],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownStrikeThrough', 'length': 33}]
+ ]
+ ],
+ [
+ # bold and nested italic text style
+ # Input text
+ [
+ '**This _word_ should be bold and italic**',
+ ],
+ # Expected text
+ [
+ 'This word should be bold and italic',
+ ],
+ # Expected text properties
+ [
+ [
+ {'col': 1, 'type': 'LspMarkdownBold', 'length': 35},
+ {'col': 6, 'type': 'LspMarkdownItalic', 'length': 4}
+ ]
+ ]
+ ],
+ [
+ # all bold and italic text style
+ # Input text
+ [
+ '***This line should be all bold and italic***',
+ ],
+ # Expected text
+ [
+ 'This line should be all bold and italic',
+ ],
+ # Expected text properties
+ [
+ [
+ {'col': 1, 'type': 'LspMarkdownItalic', 'length': 39},
+ {'col': 1, 'type': 'LspMarkdownBold', 'length': 39}
+ ]
+ ]
+ ],
+ [
+ # quoted text
+ # FIXME: The text is not quoted
+ # Input text
+ [
+ 'Text that is not quoted',
+ '> quoted text'
+ ],
+ # Expected text
+ [
+ 'Text that is not quoted',
+ '',
+ 'quoted text'
+ ],
+ # Expected text properties
+ [
+ [], [], []
+ ]
+ ],
+ [
+ # line breaks
+ # Input text
+ [
+ 'This paragraph contains ',
+ 'a soft line break',
+ '',
+ 'This paragraph contains ',
+ 'an hard line break',
+ '',
+ 'This paragraph contains an emphasis _before_\',
+ 'an hard line break',
+ '',
+ 'This paragraph contains an emphasis ',
+ '_after_ an hard line break',
+ '',
+ 'This paragraph _contains\',
+ 'an emphasis_ with an hard line break in the middle',
+ '',
+ '→ This paragraph contains an hard line break ',
+ 'and starts with the multibyte character "\u2192"',
+ '',
+ 'Line breaks `',
+ 'do\',
+ 'not ',
+ 'occur',
+ '` inside code spans'
+ ],
+ # Expected text
+ [
+ 'This paragraph contains a soft line break',
+ '',
+ 'This paragraph contains',
+ 'an hard line break',
+ '',
+ 'This paragraph contains an emphasis before',
+ 'an hard line break',
+ '',
+ 'This paragraph contains an emphasis',
+ 'after an hard line break',
+ '',
+ 'This paragraph contains',
+ 'an emphasis with an hard line break in the middle',
+ '',
+ '→ This paragraph contains an hard line break',
+ 'and starts with the multibyte character "\u2192"',
+ '',
+ 'Line breaks do\ not occur inside code spans'
+ ],
+ # Expected text properties
+ [
+ [],
+ [],
+ [],
+ [],
+ [],
+ [{'col': 37, 'type': 'LspMarkdownItalic', 'length': 6}],
+ [],
+ [],
+ [],
+ [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 5}],
+ [],
+ [{'col': 16, 'type': 'LspMarkdownItalic', 'length': 8}],
+ [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 11}],
+ [],
+ [],
+ [],
+ [],
+ [{'col': 13, 'type': 'LspMarkdownCode', 'length': 15}]
+ ]
+ ],
+ [
+ # non-breaking space characters
+ # Input text
+ [
+ '&nbsp;&nbsp;This is text.',
+ ],
+ # Expected text
+ [
+ ' This is text.',
+ ],
+ # Expected text properties
+ [
+ []
+ ]
+ ],
+ ]
+
+ var doc: dict<list<any>>
+ var text_result: list<string>
+ var props_result: list<list<dict<any>>>
+ for t in tests
+ doc = md.ParseMarkdown(t[0])
+ text_result = doc.content->deepcopy()->map((_, v) => v.text)
+ props_result = doc.content->deepcopy()->map((_, v) => v.props)
+ assert_equal(t[1], text_result, t[0]->string())
+ assert_equal(t[2], props_result, t[0]->string())
+ endfor
+enddef
+
+# Only here to because the test runner needs it
+def g:StartLangServer(): bool
+ return true
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim b/vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim
new file mode 100644
index 0000000..57c7525
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim
@@ -0,0 +1,14 @@
+vim9script
+# Unit tests for Vim Language Server Protocol (LSP) for various functionality
+
+# Test for no duplicates in helptags
+def g:Test_Helptags()
+ :helptags ../doc
+enddef
+
+# Only here to because the test runner needs it
+def g:StartLangServer(): bool
+ return true
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/run_tests.cmd b/vim/pack/downloads/opt/lsp/test/run_tests.cmd
new file mode 100644
index 0000000..863d06d
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/run_tests.cmd
@@ -0,0 +1,17 @@
+@echo off
+
+REM Script to run the unit-tests for the LSP Vim plugin on MS-Windows
+
+SETLOCAL
+SET VIMPRG="vim.exe"
+SET VIM_CMD=%VIMPRG% -u NONE -U NONE -i NONE --noplugin -N --not-a-term
+
+%VIM_CMD% -c "let g:TestName='clangd_tests.vim'" -S runner.vim
+
+echo LSP unit test results
+type results.txt
+
+findstr /I FAIL results.txt > nul 2>&1
+if %ERRORLEVEL% EQU 0 echo ERROR: Some test failed.
+if %ERRORLEVEL% NEQ 0 echo SUCCESS: All the tests passed.
+
diff --git a/vim/pack/downloads/opt/lsp/test/run_tests.sh b/vim/pack/downloads/opt/lsp/test/run_tests.sh
new file mode 100755
index 0000000..1a1e69b
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/run_tests.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# Script to run the unit-tests for the LSP Vim plugin
+
+VIMPRG=${VIMPRG:=$(which vim)}
+if [ -z "$VIMPRG" ]; then
+ echo "ERROR: vim (\$VIMPRG) is not found in PATH"
+ exit 1
+fi
+
+VIM_CMD="$VIMPRG -u NONE -U NONE -i NONE --noplugin -N --not-a-term"
+
+TESTS="clangd_tests.vim tsserver_tests.vim gopls_tests.vim not_lspserver_related_tests.vim markdown_tests.vim rust_tests.vim"
+
+RunTestsInFile() {
+ testfile=$1
+ echo "Running tests in $testfile"
+ $VIM_CMD -c "let g:TestName='$testfile'" -S runner.vim
+
+ if ! [ -f results.txt ]; then
+ echo "ERROR: Test results file 'results.txt' is not found."
+ exit 2
+ fi
+
+ cat results.txt
+
+ if grep -qw FAIL results.txt; then
+ echo "ERROR: Some test(s) in $testfile failed."
+ exit 3
+ fi
+
+ echo "SUCCESS: All the tests in $testfile passed."
+ echo
+}
+
+for testfile in $TESTS
+do
+ RunTestsInFile $testfile
+done
+
+for encoding in "utf-8" "utf-16" "utf-32"
+do
+ export LSP_OFFSET_ENCODING=$encoding
+ echo "LSP offset encoding: $LSP_OFFSET_ENCODING"
+ RunTestsInFile clangd_offsetencoding.vim
+done
+
+echo "SUCCESS: All the tests passed."
+exit 0
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/runner.vim b/vim/pack/downloads/opt/lsp/test/runner.vim
new file mode 100644
index 0000000..14c849a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/runner.vim
@@ -0,0 +1,55 @@
+vim9script
+# Script to run a language server unit tests
+# The global variable TestName should be set to the name of the file
+# containing the tests.
+
+source common.vim
+
+def LspRunTests()
+ :set nomore
+ :set debug=beep
+ delete('results.txt')
+
+ # Get the list of test functions in this file and call them
+ var fns: list<string> = execute('function /^Test_')
+ ->split("\n")
+ ->map("v:val->substitute('^def ', '', '')")
+ ->sort()
+ if fns->empty()
+ # No tests are found
+ writefile(['No tests are found'], 'results.txt')
+ return
+ endif
+ for f in fns
+ v:errors = []
+ v:errmsg = ''
+ try
+ :%bw!
+ exe $'g:{f}'
+ catch
+ call add(v:errors, $'Error: Test {f} failed with exception {v:exception} at {v:throwpoint}')
+ endtry
+ if v:errmsg != ''
+ call add(v:errors, $'Error: Test {f} generated error {v:errmsg}')
+ endif
+ if !v:errors->empty()
+ writefile(v:errors, 'results.txt', 'a')
+ writefile([$'{f}: FAIL'], 'results.txt', 'a')
+ else
+ writefile([$'{f}: pass'], 'results.txt', 'a')
+ endif
+ endfor
+enddef
+
+try
+ g:LoadLspPlugin()
+ exe $'source {g:TestName}'
+ g:StartLangServer()
+ LspRunTests()
+catch
+ writefile(['FAIL: Tests in ' .. g:TestName .. ' failed with exception ' .. v:exception .. ' at ' .. v:throwpoint], 'results.txt', 'a')
+endtry
+
+qall!
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/rust_tests.vim b/vim/pack/downloads/opt/lsp/test/rust_tests.vim
new file mode 100644
index 0000000..6dc2277
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/rust_tests.vim
@@ -0,0 +1,137 @@
+vim9script
+# Unit tests for LSP rust-analyzer client
+
+source common.vim
+source term_util.vim
+source screendump.vim
+
+var lspServers = [{
+ filetype: ['rust'],
+ path: exepath('rust-analyzer'),
+ args: []
+ }]
+call LspAddServer(lspServers)
+echomsg systemlist($'{lspServers[0].path} --version')
+
+def g:Test_LspGotoDef()
+ settagstack(0, {items: []})
+ :cd xrust_tests/src
+ try
+ silent! edit ./main.rs
+ deletebufline('%', 1, '$')
+ g:WaitForServerFileLoad(0)
+ var lines: list<string> =<< trim END
+ fn main() {
+ }
+ fn foo() {
+ }
+ fn bar() {
+ foo();
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ cursor(6, 5)
+ :LspGotoDefinition
+ assert_equal([3, 4], [line('.'), col('.')])
+ :%bw!
+ finally
+ :cd ../..
+ endtry
+enddef
+
+# Test for :LspCodeAction creating a file in the current directory
+def g:Test_LspCodeAction_CreateFile()
+ :cd xrust_tests/src
+ try
+ silent! edit ./main.rs
+ deletebufline('%', 1, '$')
+ g:WaitForServerFileLoad(0)
+ var lines: list<string> =<< trim END
+ mod foo;
+ fn main() {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(1)
+ cursor(1, 1)
+ :LspCodeAction 1
+ g:WaitForServerFileLoad(0)
+ assert_true(filereadable('foo.rs'))
+ :%bw!
+ delete('foo.rs')
+ finally
+ :cd ../..
+ endtry
+enddef
+
+# Test for :LspCodeAction creating a file in a subdirectory
+def g:Test_LspCodeAction_CreateFile_Subdir()
+ :cd xrust_tests/src
+ try
+ silent! edit ./main.rs
+ deletebufline('%', 1, '$')
+ g:WaitForServerFileLoad(0)
+ var lines: list<string> =<< trim END
+ mod baz;
+ fn main() {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(1)
+ cursor(1, 1)
+ :LspCodeAction 2
+ g:WaitForServerFileLoad(0)
+ assert_true(filereadable('baz/mod.rs'))
+ :%bw!
+ delete('baz', 'rf')
+ finally
+ :cd ../..
+ endtry
+enddef
+
+# Test for :LspCodeAction renaming a file
+def g:Test_LspCodeAction_RenameFile()
+ :cd xrust_tests/src
+ try
+ silent! edit ./main.rs
+ deletebufline('%', 1, '$')
+ g:WaitForServerFileLoad(0)
+ writefile([], 'foobar.rs')
+ var lines: list<string> =<< trim END
+ mod foobar;
+ fn main() {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ cursor(1, 5)
+ :LspRename foobaz
+ g:WaitForServerFileLoad(0)
+ assert_true(filereadable('foobaz.rs'))
+ :%bw!
+ delete('foobaz.rs')
+ finally
+ :cd ../..
+ endtry
+enddef
+
+def g:Test_ZZZ_Cleanup()
+ delete('./xrust_tests', 'rf')
+enddef
+
+# Start the rust-analyzer language server. Returns true on success and false
+# on failure.
+def g:StartLangServer(): bool
+ system('cargo new xrust_tests')
+ :cd xrust_tests/src
+ var status = false
+ try
+ status = g:StartLangServerWithFile('./main.rs')
+ finally
+ :cd ../..
+ endtry
+ return status
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/test/screendump.vim b/vim/pack/downloads/opt/lsp/test/screendump.vim
new file mode 100644
index 0000000..68d3c3f
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/screendump.vim
@@ -0,0 +1,117 @@
+" Functions shared by tests making screen dumps.
+
+source term_util.vim
+
+" Skip the rest if there is no terminal feature at all.
+if !has('terminal')
+ finish
+endif
+
+" Read a dump file "fname" and if "filter" exists apply it to the text.
+def ReadAndFilter(fname: string, filter: string): list<string>
+ var contents = readfile(fname)
+
+ if filereadable(filter)
+ # do this in the bottom window so that the terminal window is unaffected
+ wincmd j
+ enew
+ setline(1, contents)
+ exe "source " .. filter
+ contents = getline(1, '$')
+ enew!
+ wincmd k
+ redraw
+ endif
+
+ return contents
+enddef
+
+
+" Verify that Vim running in terminal buffer "buf" matches the screen dump.
+" "options" is passed to term_dumpwrite().
+" Additionally, the "wait" entry can specify the maximum time to wait for the
+" screen dump to match in msec (default 1000 msec).
+" The file name used is "dumps/{filename}.dump".
+"
+" To ignore part of the dump, provide a "dumps/{filename}.vim" file with
+" Vim commands to be applied to both the reference and the current dump, so
+" that parts that are irrelevant are not used for the comparison. The result
+" is NOT written, thus "term_dumpdiff()" shows the difference anyway.
+"
+" Optionally an extra argument can be passed which is prepended to the error
+" message. Use this when using the same dump file with different options.
+" Returns non-zero when verification fails.
+func VerifyScreenDump(buf, filename, options, ...)
+ let reference = 'dumps/' . a:filename . '.dump'
+ let filter = 'dumps/' . a:filename . '.vim'
+ let testfile = 'failed/' . a:filename . '.dump'
+
+ let max_loops = get(a:options, 'wait', 1000) / 10
+
+ " Starting a terminal to make a screendump is always considered flaky.
+ let g:test_is_flaky = 1
+
+ " wait for the pending updates to be handled.
+ call TermWait(a:buf)
+
+ " Redraw to execute the code that updates the screen. Otherwise we get the
+ " text and attributes only from the internal buffer.
+ redraw
+
+ if filereadable(reference)
+ let refdump = ReadAndFilter(reference, filter)
+ else
+ " Must be a new screendump, always fail
+ let refdump = []
+ endif
+
+ let did_mkdir = 0
+ if !isdirectory('failed')
+ let did_mkdir = 1
+ call mkdir('failed')
+ endif
+
+ let i = 0
+ while 1
+ " leave some time for updating the original window
+ sleep 10m
+ call delete(testfile)
+ call term_dumpwrite(a:buf, testfile, a:options)
+ let testdump = ReadAndFilter(testfile, filter)
+ if refdump == testdump
+ call delete(testfile)
+ if did_mkdir
+ call delete('failed', 'd')
+ endif
+ break
+ endif
+ if i == max_loops
+ " Leave the failed dump around for inspection.
+ if filereadable(reference)
+ let msg = 'See dump file difference: call term_dumpdiff("testdir/' .. testfile .. '", "testdir/' .. reference .. '")'
+ if a:0 == 1
+ let msg = a:1 . ': ' . msg
+ endif
+ if len(testdump) != len(refdump)
+ let msg = msg . '; line count is ' . len(testdump) . ' instead of ' . len(refdump)
+ endif
+ else
+ let msg = 'See new dump file: call term_dumpload("testdir/' .. testfile .. '")'
+ " no point in retrying
+ let g:run_nr = 10
+ endif
+ for i in range(len(refdump))
+ if i >= len(testdump)
+ break
+ endif
+ if testdump[i] != refdump[i]
+ let msg = msg . '; difference in line ' . (i + 1) . ': "' . testdump[i] . '"'
+ endif
+ endfor
+ call assert_report(msg)
+ return 1
+ endif
+ let i += 1
+ endwhile
+ return 0
+endfunc
diff --git a/vim/pack/downloads/opt/lsp/test/start_tsserver.vim b/vim/pack/downloads/opt/lsp/test/start_tsserver.vim
new file mode 100644
index 0000000..3896c70
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/start_tsserver.vim
@@ -0,0 +1,10 @@
+vim9script
+source common.vim
+g:LoadLspPlugin()
+var lspServers = [{
+filetype: ['typescript', 'javascript'],
+ path: exepath('typescript-language-server'),
+ args: ['--stdio']
+}]
+g:LspAddServer(lspServers)
+g:StartLangServerWithFile('Xtest.ts')
diff --git a/vim/pack/downloads/opt/lsp/test/term_util.vim b/vim/pack/downloads/opt/lsp/test/term_util.vim
new file mode 100644
index 0000000..7b1779f
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/term_util.vim
@@ -0,0 +1,125 @@
+" Functions about terminal shared by several tests
+
+" Wrapper around term_wait().
+" The second argument is the minimum time to wait in msec, 10 if omitted.
+func TermWait(buf, ...)
+ let wait_time = a:0 ? a:1 : 10
+ call term_wait(a:buf, wait_time)
+endfunc
+
+" Run Vim with "arguments" in a new terminal window.
+" By default uses a size of 20 lines and 75 columns.
+" Returns the buffer number of the terminal.
+"
+" Options is a dictionary, these items are recognized:
+" "keep_t_u7" - when 1 do not make t_u7 empty (resetting t_u7 avoids clearing
+" parts of line 2 and 3 on the display)
+" "rows" - height of the terminal window (max. 20)
+" "cols" - width of the terminal window (max. 78)
+" "statusoff" - number of lines the status is offset from default
+" "wait_for_ruler" - if zero then don't wait for ruler to show
+" "no_clean" - if non-zero then remove "--clean" from the command
+func RunVimInTerminal(arguments, options)
+ " If Vim doesn't exit a swap file remains, causing other tests to fail.
+ " Remove it here.
+ call delete(".swp")
+
+ if exists('$COLORFGBG')
+ " Clear $COLORFGBG to avoid 'background' being set to "dark", which will
+ " only be corrected if the response to t_RB is received, which may be too
+ " late.
+ let $COLORFGBG = ''
+ endif
+
+ " Make a horizontal and vertical split, so that we can get exactly the right
+ " size terminal window. Works only when the current window is full width.
+ call assert_equal(&columns, winwidth(0))
+ split
+ vsplit
+
+ " Always do this with 256 colors and a light background.
+ set t_Co=256 background=light
+ hi Normal ctermfg=NONE ctermbg=NONE
+
+ " Make the window 20 lines high and 75 columns, unless told otherwise or
+ " 'termwinsize' is set.
+ let rows = get(a:options, 'rows', 20)
+ let cols = get(a:options, 'cols', 75)
+ let statusoff = get(a:options, 'statusoff', 1)
+
+ if get(a:options, 'keep_t_u7', 0)
+ let reset_u7 = ''
+ else
+ let reset_u7 = ' --cmd "set t_u7=" '
+ endif
+
+ let cmd = exepath('vim') .. ' -u NONE --clean --not-a-term --cmd "set enc=utf8"'.. reset_u7 .. a:arguments
+
+ if get(a:options, 'no_clean', 0)
+ let cmd = substitute(cmd, '--clean', '', '')
+ endif
+
+ let options = #{curwin: 1}
+ if &termwinsize == ''
+ let options.term_rows = rows
+ let options.term_cols = cols
+ endif
+
+ " Accept other options whose name starts with 'term_'.
+ call extend(options, filter(copy(a:options), 'v:key =~# "^term_"'))
+
+ let buf = term_start(cmd, options)
+
+ if &termwinsize == ''
+ " in the GUI we may end up with a different size, try to set it.
+ if term_getsize(buf) != [rows, cols]
+ call term_setsize(buf, rows, cols)
+ endif
+ call assert_equal([rows, cols], term_getsize(buf))
+ else
+ let rows = term_getsize(buf)[0]
+ let cols = term_getsize(buf)[1]
+ endif
+
+ call TermWait(buf)
+
+ if get(a:options, 'wait_for_ruler', 1)
+ " Wait for "All" or "Top" of the ruler to be shown in the last line or in
+ " the status line of the last window. This can be quite slow (e.g. when
+ " using valgrind).
+ " If it fails then show the terminal contents for debugging.
+ try
+ call g:WaitFor({-> len(term_getline(buf, rows)) >= cols - 1 || len(term_getline(buf, rows - statusoff)) >= cols - 1})
+ catch /timed out after/
+ let lines = map(range(1, rows), {key, val -> term_getline(buf, val)})
+ call assert_report('RunVimInTerminal() failed, screen contents: ' . join(lines, "<NL>"))
+ endtry
+ endif
+
+ return buf
+endfunc
+
+" Stop a Vim running in terminal buffer "buf".
+func StopVimInTerminal(buf, kill = 1)
+ call assert_equal("running", term_getstatus(a:buf))
+
+ " Wait for all the pending updates to terminal to complete
+ call TermWait(a:buf)
+
+ " CTRL-O : works both in Normal mode and Insert mode to start a command line.
+ " In Command-line it's inserted, the CTRL-U removes it again.
+ call term_sendkeys(a:buf, "\<C-O>:\<C-U>qa!\<cr>")
+
+ " Wait for all the pending updates to terminal to complete
+ call TermWait(a:buf)
+
+ " Wait for the terminal to end.
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(a:buf))})
+
+ " If the buffer still exists forcefully wipe it.
+ if a:kill && bufexists(a:buf)
+ exe a:buf .. 'bwipe!'
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/vim/pack/downloads/opt/lsp/test/tsserver_tests.vim b/vim/pack/downloads/opt/lsp/test/tsserver_tests.vim
new file mode 100644
index 0000000..99e2836
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/test/tsserver_tests.vim
@@ -0,0 +1,43 @@
+vim9script
+# Unit tests for Vim Language Server Protocol (LSP) typescript client
+
+source common.vim
+source term_util.vim
+source screendump.vim
+
+var lspServers = [{
+ filetype: ['typescript', 'javascript'],
+ path: exepath('typescript-language-server'),
+ args: ['--stdio']
+ }]
+call LspAddServer(lspServers)
+echomsg systemlist($'{lspServers[0].path} --version')
+
+# Test for auto-completion. Make sure that only keywords that matches with the
+# keyword before the cursor are shown.
+# def g:Test_LspCompletion1()
+# var lines =<< trim END
+# const http = require('http')
+# http.cr
+# END
+# writefile(lines, 'Xcompletion1.js')
+# var buf = g:RunVimInTerminal('--cmd "silent so start_tsserver.vim" Xcompletion1.js', {rows: 10, wait_for_ruler: 1})
+# sleep 5
+# term_sendkeys(buf, "GAe")
+# g:TermWait(buf)
+# g:VerifyScreenDump(buf, 'Test_tsserver_completion_1', {})
+# term_sendkeys(buf, "\<BS>")
+# g:TermWait(buf)
+# g:VerifyScreenDump(buf, 'Test_tsserver_completion_2', {})
+#
+# g:StopVimInTerminal(buf)
+# delete('Xcompletion1.js')
+# enddef
+
+# Start the typescript language server. Returns true on success and false on
+# failure.
+def g:StartLangServer(): bool
+ return g:StartLangServerWithFile('Xtest.ts')
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/snippets/plugin/snippets.vim b/vim/pack/downloads/opt/snippets/plugin/snippets.vim
new file mode 120000
index 0000000..1b5db26
--- /dev/null
+++ b/vim/pack/downloads/opt/snippets/plugin/snippets.vim
@@ -0,0 +1 @@
+/home/mccd/.dotfiles/vim/pack/downloads/opt/snippets/snippets.vim \ No newline at end of file
diff --git a/vim/swap/%home%mccd%.xsession.swp b/vim/swap/%home%mccd%.xsession.swp
new file mode 100644
index 0000000..032030c
--- /dev/null
+++ b/vim/swap/%home%mccd%.xsession.swp
Binary files differ
diff --git a/vim/swap/%home%mccd.swp b/vim/swap/%home%mccd.swp
new file mode 100644
index 0000000..5b5e172
--- /dev/null
+++ b/vim/swap/%home%mccd.swp
Binary files differ
diff --git a/xenodm/Xresources b/xenodm/Xresources
new file mode 100644
index 0000000..acec2cc
--- /dev/null
+++ b/xenodm/Xresources
@@ -0,0 +1,82 @@
+! $OpenBSD: Xresources.in,v 1.4 2022/11/07 17:12:06 matthieu Exp $
+!
+!
+!
+!
+!
+
+
+
+
+xlogin*login.translations: #override \
+ <Key>F1: set-session-argument(failsafe) finish-field()\n\
+ <Key>Left: move-backward-character()\n\
+ <Key>Right: move-forward-character()\n\
+ <Key>Home: move-to-begining()\n\
+ <Key>End: move-to-end()\n\
+ Ctrl<Key>KP_Enter: set-session-argument(failsafe) finish-field()\n\
+ <Key>KP_Enter: set-session-argument() finish-field()\n\
+ Ctrl<Key>Return: set-session-argument(failsafe) finish-field()\n\
+ <Key>Return: set-session-argument() finish-field()
+
+xlogin*greeting:
+xlogin*namePrompt: Login:
+xlogin*fail: Login incorrect
+
+#if WIDTH > 800
+xlogin*greetFace: IosevkaAile-22:bold:italic:dpi=75
+xlogin*face: IosevkaAile-16:dpi=75
+xlogin*promptFace: IosevkaAile-16:bold:dpi=75
+xlogin*failFace: IosevkaAile-16:bold:dpi=75
+#else
+xlogin*greetFace: IosevkaAile-18:bold:italic:dpi=75
+xlogin*face: IosevkaAile-12:dpi=75
+xlogin*promptFace: IosevkaAile-12:bold:dpi=75
+xlogin*failFace: IosevkaAile-12:bold:dpi=75
+#endif
+
+#if !(defined(bpp1) || defined(bpp4) || defined(bpp8) || defined(bpp15))
+# if PLANES < 4
+# ifndef bpp1
+# define bpp1
+# endif
+# else
+# if PLANES > 4
+# if PLANES > 8
+# ifndef bpp15
+# define bpp15
+# endif
+# else
+# ifndef bpp8
+# define bpp8
+# endif bpp8
+# endif
+# else
+# ifndef bpp4
+# define bpp4
+# endif
+# endif
+# endif
+#endif /* If manual override */
+
+#ifndef bpp1
+xlogin*borderWidth: 0
+xlogin*frameWidth: 2
+xlogin.Login.height: 200
+xlogin.Login.width: 400
+xlogin.Login.y: 600
+xlogin.Login.frameWidth: 10
+xlogin.Login.innerFramesWidth: 0
+xlogin*innerFramesWidth: 20
+Xcursor.theme: Adwaita
+Xcursor.size: 34
+
+xlogin.Login.sepWidth: 0
+
+xlogin.Login.background: #FFFFFF
+xlogin.Login.foreground: #000000
+xlogin.Login.failColor: #b00035
+xlogin.Login.inpColor: #FFFFFF
+xlogin.Login.promptColor: #000000
+xlogin.Login.hiColor: #FFFFFF
+xlogin.Login.shdColor: #FFFFFF
diff --git a/xenodm/Xsetup_0 b/xenodm/Xsetup_0
new file mode 100755
index 0000000..ca12044
--- /dev/null
+++ b/xenodm/Xsetup_0
@@ -0,0 +1,19 @@
+#!/bin/sh
+# $OpenBSD: Xsetup_0.in,v 1.1 2021/08/30 15:38:27 matthieu Exp $
+
+prefix="/usr/X11R6"
+exec_prefix="${prefix}"
+
+${exec_prefix}/bin/xsetroot -solid \#FFFFFF
+
+/usr/X11R6/bin/xset fp+ /usr/local/share/fonts/iosevka
+
+# install package openbsd-backgrounds
+# then uncomment:
+#
+# if test -x /usr/local/bin/openbsd-wallpaper
+# then
+# /usr/local/bin/openbsd-wallpaper
+# fi
+
+# sxpm OpenBSD.xpm &
diff --git a/xsession b/xsession
new file mode 100755
index 0000000..614d452
--- /dev/null
+++ b/xsession
@@ -0,0 +1,14 @@
+. ~/.profile
+xrdb -load ~/.Xresources &
+xsetroot -solid '#484C54' &
+#xclock -digital -geometry +2250+1400 -face "Ttyp0:pixelsize=24:antialias=false:autohint=false" &
+gsettings set org.gnome.desktop.interface document-font-name 'Iosevka Aile' &
+gsettings set org.gnome.desktop.interface font-name 'Iosevka Aile' &
+gsettings set org.gnome.desktop.interface cursor-size 32 &
+gsettings set org.gnome.desktop.interface cursor-theme Adwaita &
+ulimit -Sc 0 &
+xinput set-prop "/dev/wsmouse" "WS Pointer Wheel Emulation" 1 &
+xinput set-prop "/dev/wsmouse" "WS Pointer Wheel Emulation Button" 2 &
+xinput set-prop "/dev/wsmouse" "WS Pointer Wheel Emulation Axes" 6 7 4 5 &
+setxkbmap -layout us -variant colemak -option "caps:capslock" &
+dbus-run-session cwm