Compare commits

...

42 Commits

Author SHA1 Message Date
James
28cf3fc902
Merge 77c10671ed into 755512ed60 2024-09-13 15:10:40 +00:00
dundargoc
755512ed60 ci: don't add reviewers for PRs created by a bot
This will ensure automatic backports created by the backport action does
not request reviewers (since the commit in question has already been
vetted and merged), but manual backports created by users does request
reviewers as these commits has not been vetted previously.
2024-09-13 16:58:15 +02:00
dundargoc
4c23b83456 ci: add needs:backport label on backport PRs with conflict
This makes it easy to keep track of which backport PRs have failed and
need manual intervention to fix.
2024-09-13 16:49:35 +02:00
dundargoc
5284a2a793 build: bump unibilium to v2.1.2
There is no real practical difference from previous commit except that
this is a tagged release.
2024-09-13 15:56:37 +02:00
Riley Bruins
b9b408a56c
feat(treesitter): start moving get_parser to return nil #30313
**Problem:** `vim.treesitter.get_parser` will throw an error if no parser
can be found.

- This means the caller is responsible for wrapping it in a `pcall`,
  which is easy to forget
- It also makes it slightly harder to potentially memoize `get_parser`
  in the future
- It's a bit unintuitive since many other `get_*` style functions
  conventionally return `nil` if no object is found (e.g. `get_node`,
  `get_lang`, `query.get`, etc.)

**Solution:** Return `nil` if no parser can be found or created

- This requires a function signature change, and some new assertions in
  places where the parser will always (or should always) be found.
- This commit starts by making this change internally, since it is
  breaking. Eventually it will be rolled out to the public API.
2024-09-13 05:09:11 -07:00
James Trew
8654a97006
fix(lsp): handle empty call hierarchy items #30349
Ensure that the function `pick_call_hierarchy_item` correctly handles
the case where `call_hierarchy_items` is nil or an empty table. This
prevents potential errors when the function is called with no items.
2024-09-13 04:59:49 -07:00
dundargoc
057314345a ci: enable automerge by default when backporting
This will automatically merge backported PRs without human intervention
if the tests pass.
2024-09-13 12:31:33 +02:00
Justin M. Keyes
deac7df80a
refactor(stream.c): unused params in stream_close #30356 2024-09-12 09:16:57 -07:00
Christian Clason
ae917dbd06 fix(treesitter): sync queries from upstream 2024-09-12 13:41:15 +02:00
Christian Clason
c65153893a build(deps): bump tree-sitter-c to v0.23.0 2024-09-12 13:41:15 +02:00
Christian Clason
f6cc0394ae build(deps): bump tree-sitter-markdown to v0.3.2 2024-09-12 13:41:15 +02:00
Christian Clason
f347c292d1 build(deps): bump tree-sitter-lua to v0.2.0 2024-09-12 13:41:15 +02:00
zeertzjq
48c14d3544
vim-patch:9.1.0725: filetype: swiftinterface files are not recognized (#30350)
Problem:  filetype: swiftinterface files are not recognized
Solution: Detect '*.swiftinterface' files as swift filetype
          (LosFarmosCTL)

closes: vim/vim#15658

03cac4b70d

Co-authored-by: LosFarmosCTL <80157503+LosFarmosCTL@users.noreply.github.com>
2024-09-12 15:16:58 +08:00
Justin M. Keyes
5931f780e0
feat(log): use "ui" as default name for TUI client #30345
The default "session name" for the builtin TUI is "ui".

before:

    INF 2024-09-10T14:57:35.385 hello.sock os_exit:692: Nvim exit: 1
    INF 2024-09-10T14:57:35.388 ?.4543     os_exit:692: Nvim exit: 1

after:

    INF 2024-09-10T14:59:19.919 hello.sock os_exit:692: Nvim exit: 1
    INF 2024-09-10T14:59:19.922 ui.5684    os_exit:692: Nvim exit: 1
2024-09-11 17:25:00 -07:00
Gregory Anders
98ba65b8be
fix: replace NVIM with Nvim in default titlestring (#30348) 2024-09-11 20:32:08 +00:00
glepnir
f9bf64d746
fix(lsp): check buffer is loaded and valid #30330
Problem: buffer mabye not valid when callback handler invoke.

Soliton: check buffer is valid and loaded in handler.
2024-09-11 08:11:09 -07:00
Justin M. Keyes
a30afeeb85
Merge #30342 :checkhealth completion 2024-09-11 03:01:48 -07:00
Justin M. Keyes
7b680e0ca9 test(health): "test_plug/health/init.lua" completes as "test_plug" 2024-09-11 11:40:56 +02:00
Christian Clason
8501fe621a build(deps): bump unibilium to HEAD - ab28a2ddb 2024-09-11 11:10:40 +02:00
Christian Clason
a5bd6665b0 fix(scripts): update bundled dependencies in bump_deps 2024-09-11 11:10:40 +02:00
Christian Clason
4ac733f6ef vim-patch:fc72a2f: runtime(idlang): update syntax script
closes: vim/vim#15419

fc72a2fa48

Co-authored-by: Joe Sapp <992873+sappjw@users.noreply.github.com>
2024-09-11 09:47:30 +02:00
Christian Clason
c1a4b8680b vim-patch:e401576: runtime(spec): Recognize epoch when making spec changelog in ftplugin
closes: vim/vim#15537

e40157641c

Co-authored-by: fundawang <fundawang@yeah.net>
2024-09-11 09:47:30 +02:00
Christian Clason
f6579a4db1 vim-patch:2241f08: runtime(spec): add file triggers to syntax script
closes: vim/vim#15569

2241f0845f

Co-authored-by: fundawang <fundawang@yeah.net>
2024-09-11 09:47:30 +02:00
Christian Clason
608ef83fc6 vim-patch:077d1d2: runtime(make): add compiler/make.vim to reset compiler plugin settings
closes: vim/vim#15645

077d1d2cff

Co-authored-by: Konfekt <Konfekt@users.noreply.github.com>
Co-authored-by: K.Takata <kentkt@csc.jp>
2024-09-11 09:47:30 +02:00
Christian Clason
57db94235c vim-patch:a9ae38d: runtime(java): Recognise all available standard doclet tags
* Complement the tag set with @spec, {@systemProperty},
  {@summary}, @provides, @uses, @hidden, and {@index}.
* Do not hoard all tags under a single highlighting group.
* Skip over nested balanced braces in inline tags.
* Observe that tag names are case sensitive: both {@docRoot}
  and {@inheritDoc} are valid, whereas {@inheritdoc} and
  {@docroot} are not.
* In the @see tag arguments, allow for:
  - module name prefixes (e.g. java.base/java.lang.String);
  - references to arbitrary URI fragments (e.g. ##foo);
  - matching any tag variation arguments on the next line.
* Test directives and tags for Java module declarations.
* Enforce the word end for "module-info" candidates.

References:
https://bugs.openjdk.org/browse/JDK-8226279 (@spec)
https://bugs.openjdk.org/browse/JDK-8214559 ({@systemProperty})
https://bugs.openjdk.org/browse/JDK-8173425 ({@summary})
https://bugs.openjdk.org/browse/JDK-8160196 (@provides & @uses)
https://bugs.openjdk.org/browse/JDK-8073100 (@hidden)
https://bugs.openjdk.org/browse/JDK-8044243 ({@index})
https://docs.oracle.com/en/java/javase/21/docs/specs/javadoc/doc-comment-spec.html
https://github.com/openjdk/jdk/blob/jdk-21-ga/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java

closes: vim/vim#15652

a9ae38dc3f

Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com>
2024-09-11 09:47:30 +02:00
Christian Clason
cdab8c8ebb vim-patch:c0982f9: runtime(dosini): Update syntax script, spellcheck comments only
By default spell checking is enabled for all text, but adding
`contains=@Spell` to syntax rules restricts spell checking to those
syntax rules.  See `:help spell-syntax` for full details.

Variable names and headers are far more likely than comments to contain
spelling errors, so only enable spell checking in comments.

Introduced in https://github.com/xuhdev/syntax-dosini.vim/pull/8

cc @tobinjt

closes: vim/vim#15655

c0982f9f79

Co-authored-by: John Tobin <johntobin@johntobin.ie>
2024-09-11 09:47:30 +02:00
Christian Clason
830cf054bc vim-patch:d30ffdc: runtime(pandoc): Update compiler plugin to use actual 'spelllang'
Previously these would be cached in buffer-local variables and
would not change on :compiler pandoc

closes: vim/vim#15642

d30ffdca49

Co-authored-by: Christian Brabandt <cb@256bit.org>
2024-09-11 09:47:30 +02:00
Christian Clason
def6111118 vim-patch:7cc0e91: runtime(groff): Add compiler plugin for groff
Groff MOM (Macros for Manuscripts) is a macro package for the GNU
troff (groff) typesetting system, a light-weight alternative
to LaTeX for professional-quality documents.

closes: vim/vim#15646

7cc0e9145d

Co-authored-by: Konfekt <Konfekt@users.noreply.github.com>
2024-09-11 09:47:30 +02:00
monkoose
237d2aef4d
fix(health): return correct name from 'path2name()'
`path2name()` function doesn't process `'pluginname/health/init.lua'` correctly. Instead of retruning `'pluginname'` it returns `'pluginname.health'`
2024-09-11 07:55:37 +03:00
zeertzjq
15bfdf73ea
vim-patch:9.1.0727: too many strlen() calls in option.c (#30338)
Problem:  too many strlen() calls in option.c
Solution: refactor the code to reduce the number of strlen() calls
          (John Marriott)

closes: vim/vim#15604

95dacbb5fd

Co-authored-by: John Marriott <basilisk@internode.on.net>
2024-09-10 23:31:07 +00:00
dundargoc
f289161c3c test: add termkey unit tests
Skipped importing the following unit tests from libtermkey as they'd
require introducing a lot of unused code or require more effort to port
than is probably worth:

- 05read
- 12strpkey
- 20canon
- 40ti-override
2024-09-11 00:36:14 +02:00
zeertzjq
4b98d38870
vim-patch:d657d3d: runtime(doc): clarify the effect of the timeout for search()-functions (#30337)
related: vim/vim#15657
related: vim/vim#15404

d657d3d8fd

Co-authored-by: Christian Brabandt <cb@256bit.org>
2024-09-11 06:09:58 +08:00
bfredl
4c5bce9cb4
Merge pull request #30295 from glepnir/nfloat
fix(highlight): floating windows inherit NormalFloat from global-ns
2024-09-10 11:11:07 +02:00
Justin M. Keyes
5d7853f229
refactor(os/input.c): rename os_inchar => input_get #30327
Problem:
The name `os_inchar` (from Vim's old `mch_inchar`) is ambiguous:
"inchar" sounds like it could be reading or enqueuing (setting) input.
Its docstring is also ambiguous.

Solution:
- Rename `os_inchar` to `input_get`.
- Write some mf'ing docstrings.
- Add assert() in TRY_READ().
2024-09-10 01:14:18 -07:00
tris203
f279d1ae33 fix(lsp): handle encoding bounds in str_utfindex_enc
Problem:
str_utfindex_enc could return an error if the index was longer than the
line length. This was handled in each of the calls to it individually

Solution:
* Fix the call at the source level so that if the index is higher than
  the line length, utf length is returned
2024-09-10 09:25:04 +02:00
LosFarmosCTL
9ddfcb64bf
fix(runtime): add remaining missing commentstrings (#30252) 2024-09-10 15:01:25 +08:00
glepnir
8e81212e15 fix(highlight): floating windows inherit NormalFloat from global-ns
Problem:
floating windows did not correctly inherit the NormalFloat highlight
group from the global namespace when it was not defined in the window-specific
namespace. This led to floating windows losing their background highlight when
switching between namespaces.

Solution:
Updated the window highlight logic in update_window_hl() to handle the fallback.

This fix resolves issues with floating window backgrounds not displaying as expected
in certain namespace configurations.
2024-09-10 13:31:42 +08:00
Justin M. Keyes
648d6426c8
fix(server): CID 509282: DEADCODE #30316
listen_addr cannot be NULL at this point.
2024-09-09 05:14:47 -07:00
Justin M. Keyes
f0334c2c71
Merge #30312 from justinmk/testslashes 2024-09-09 04:00:35 -07:00
Justin M. Keyes
c8e3618e0e fix(test): "tempdir not a directory" in CI logs
$NVIM_LOG_FILE: /Users/runner/work/neovim/neovim/build/.nvimlog
    WRN 2024-09-08T21:48:13.279 ?.21134    vim_mktempdir:3281: $TMPDIR tempdir not a directory (or does not exist): TMPDIR-should-be-ignored
    WRN 2024-09-08T21:48:13.312 ?.21137    vim_mktempdir:3281: $TMPDIR tempdir not a directory (or does not exist): TMPDIR-should-be-ignored
2024-09-09 12:23:54 +02:00
Justin M. Keyes
ed832b9ddf refactor(test): rename alter_slashes, invert its behavior
- `alter_slashes` belongs in `testutil.lua`, not `testnvim.lua`.
- `alter_slashes` is an unusual name. Rename it to `fix_slashes`.
- invert its behavior, to emphasize that `/` slashes are the preferred,
  pervasive convention, not `\` slashes.
2024-09-09 12:23:54 +02:00
James Tirta Halim
77c10671ed refactor: use libc stp[n]cpy 2024-04-18 20:07:56 +07:00
82 changed files with 1980 additions and 621 deletions

View File

@ -26,3 +26,20 @@ jobs:
pull_title: "${pull_title}"
label_pattern: "^ci:backport ([^ ]+)$"
github_token: ${{ steps.app-token.outputs.token }}
- name: Create failed backport label
if: ${{ steps.backport.outputs.was_successful == 'false' }}
uses: actions/github-script@v7
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['needs:backport']
})
- name: Enable automerge
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge --rebase --auto ${{ steps.backport.outputs.created_pull_numbers }}

View File

@ -5,7 +5,7 @@ on:
workflow_call:
jobs:
request-reviewer:
if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false
if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false && !endsWith(github.actor, '[bot]')
runs-on: ubuntu-latest
permissions:
pull-requests: write

View File

@ -49,6 +49,8 @@ endif()
check_function_exists(fseeko HAVE_FSEEKO)
check_function_exists(readv HAVE_READV)
check_function_exists(readlink HAVE_READLINK)
check_function_exists(stpcpy HAVE_STPCPY)
check_function_exists(stpncpy HAVE_STPNCPY)
check_function_exists(strnlen HAVE_STRNLEN)
check_function_exists(strcasecmp HAVE_STRCASECMP)
check_function_exists(strncasecmp HAVE_STRNCASECMP)

View File

@ -7,8 +7,8 @@ LUAJIT_SHA256 2b5514bd6a6573cb6111b43d013e952cbaf46762d14ebe26c872ddb80b5a84e0
LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz
LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333
UNIBILIUM_URL https://github.com/neovim/unibilium/archive/d72c3598e7ac5d1ebf86ee268b8b4ed95c0fa628.tar.gz
UNIBILIUM_SHA256 9c4747c862ab5e3076dcf8fa8f0ea7a6b50f20ec5905618b9536655596797487
UNIBILIUM_URL https://github.com/neovim/unibilium/archive/v2.1.2.tar.gz
UNIBILIUM_SHA256 370ecb07fbbc20d91d1b350c55f1c806b06bf86797e164081ccc977fc9b3af7a
LUV_URL https://github.com/luvit/luv/releases/download/1.48.0-2/luv-1.48.0-2.tar.gz
LUV_SHA256 2c3a1ddfebb4f6550293a40ee789f7122e97647eede51511f57203de48c03b7a
@ -38,18 +38,18 @@ LIBICONV_SHA256 8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313
UTF8PROC_URL https://github.com/JuliaStrings/utf8proc/archive/3de4596fbe28956855df2ecb3c11c0bbc3535838.tar.gz
UTF8PROC_SHA256 fb4a16bb659b58afb7f921fcc8928d0b3c1fcab135366c8a4f9ca7de1b1cfada
TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.21.3.tar.gz
TREESITTER_C_SHA256 75a3780df6114cd37496761c4a7c9fd900c78bee3a2707f590d78c0ca3a24368
TREESITTER_LUA_URL https://github.com/tree-sitter-grammars/tree-sitter-lua/archive/v0.1.0.tar.gz
TREESITTER_LUA_SHA256 230cfcbfa74ed1f7b8149e9a1f34c2efc4c589a71fe0f5dc8560622f8020d722
TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.23.0.tar.gz
TREESITTER_C_SHA256 ee58c925e2e507c23d735aad46bf7fb0af31ca06d6f4f41bc008216d9232b0cb
TREESITTER_LUA_URL https://github.com/tree-sitter-grammars/tree-sitter-lua/archive/v0.2.0.tar.gz
TREESITTER_LUA_SHA256 6c41227cd0a59047b19d31f0031d4d901f08bfd78d6fc7f55c89e5b8374c794e
TREESITTER_VIM_URL https://github.com/neovim/tree-sitter-vim/archive/v0.4.0.tar.gz
TREESITTER_VIM_SHA256 9f856f8b4a10ab43348550fa2d3cb2846ae3d8e60f45887200549c051c66f9d5
TREESITTER_VIMDOC_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v3.0.0.tar.gz
TREESITTER_VIMDOC_SHA256 a639bf92bf57bfa1cdc90ca16af27bfaf26a9779064776dd4be34c1ef1453f6c
TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/archive/v0.4.0.tar.gz
TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e
TREESITTER_MARKDOWN_URL https://github.com/MDeiml/tree-sitter-markdown/archive/v0.2.3.tar.gz
TREESITTER_MARKDOWN_SHA256 4909d6023643f1afc3ab219585d4035b7403f3a17849782ab803c5f73c8a31d5
TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz
TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5
TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.23.0.tar.gz
TREESITTER_SHA256 6403b361b0014999e96f61b9c84d6950d42f0c7d6e806be79382e0232e48a11b

View File

@ -4,6 +4,8 @@ They are used with the ":compiler" command.
These scripts usually set options, for example 'errorformat'.
See ":help write-compiler-plugin".
To undo the effect of a compiler plugin, use the make compiler plugin.
If you want to write your own compiler plugin, have a look at the other files
for how to do it, the format is simple.

View File

@ -0,0 +1,45 @@
" Vim compiler file
" Compiler: Groff
" Maintainer: Konfekt
" Last Change: 2024 Sep 8
"
" Expects output file extension, say `:make html` or `:make pdf`.
" Supported devices as of Sept 2024 are: (x)html, pdf, ps, dvi, lj4, lbp ...
" Adjust command-line flags, language, encoding by buffer-local/global variables
" groff_compiler_args, groff_compiler_lang, and groff_compiler_encoding,
" which default to '', &spelllang and 'utf8'.
if exists("current_compiler")
finish
endif
let s:keepcpo = &cpo
set cpo&vim
let current_compiler = 'groff'
silent! function s:groff_compiler_lang()
let lang = get(b:, 'groff_compiler_lang',
\ &spell ? matchstr(&spelllang, '^\a\a') : '')
if lang ==# 'en' | let lang = '' | endif
return empty(lang) ? '' : '-m'..lang
endfunction
" Requires output format (= device) to be set by user after :make.
execute 'CompilerSet makeprg=groff'..escape(
\ ' '..s:groff_compiler_lang()..
\ ' -K'..get(b:, 'groff_compiler_encoding', get(g:, 'groff_compiler_encoding', 'utf8'))..
\ ' '..get(b:, 'groff_compiler_args', get(g:, 'groff_compiler_args', ''))..
\ ' -mom -T$* -- %:S > %:r:S.$*', ' ')
" From Gavin Freeborn's https://github.com/Gavinok/vim-troff under Vim License
" https://github.com/Gavinok/vim-troff/blob/91017b1423caa80aba541c997909a4f810edd275/compiler/troff.vim#L39
CompilerSet errorformat=%o:<standard\ input>\ (%f):%l:%m,
\%o:\ <standard\ input>\ (%f):%l:%m,
\%o:%f:%l:%m,
\%o:\ %f:%l:%m,
\%f:%l:\ macro\ %trror:%m,
\%f:%l:%m,
\%W%tarning:\ file\ '%f'\\,\ around\ line\ %l:,%Z%m
let &cpo = s:keepcpo
unlet s:keepcpo

13
runtime/compiler/make.vim Normal file
View File

@ -0,0 +1,13 @@
" Vim compiler plugin
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2024 Sep 10
" Original Author: Konfekt
"
" This compiler plugin is used to reset previously set compiler options.
if exists("g:current_compiler") | unlet g:current_compiler | endif
if exists("b:current_compiler") | unlet b:current_compiler | endif
CompilerSet makeprg&
CompilerSet errorformat&

View File

@ -1,10 +1,12 @@
" Vim compiler file
" Compiler: Pandoc
" Maintainer: Konfekt
" Last Change: 2024 Aug 20
" Last Change: 2024 Sep 8
"
" Expects output file extension, say `:make html` or `:make pdf`.
" Passes additional arguments to pandoc, say `:make html --self-contained`.
" Adjust command-line flags by buffer-local/global variable
" b/g:pandoc_compiler_args which defaults to empty.
if exists("current_compiler")
finish
@ -40,18 +42,21 @@ silent! function s:PandocFiletype(filetype) abort
endif
endfunction
let b:pandoc_compiler_from = get(b:, 'pandoc_compiler_from', s:PandocFiletype(&filetype))
let b:pandoc_compiler_lang = get(b:, 'pandoc_compiler_lang', &spell ? matchstr(&spelllang, '^\a\a') : '')
silent! function s:PandocLang()
let lang = get(b:, 'pandoc_compiler_lang',
\ &spell ? matchstr(&spelllang, '^\a\a') : '')
if lang ==# 'en' | let lang = '' | endif
return empty(lang) ? '' : '--metadata lang='..lang
endfunction
execute 'CompilerSet makeprg=pandoc'..escape(
\ ' --standalone' .
\ (b:pandoc_compiler_from ==# 'markdown' && (getline(1) =~# '^%\s\+\S\+' || (search('^title:\s+\S+', 'cnw') > 0)) ?
\ '' : ' --metadata title=%:t:r:S') .
\ (empty(b:pandoc_compiler_lang) ?
\ '' : ' --metadata lang='..b:pandoc_compiler_lang) .
\ ' --from='..b:pandoc_compiler_from .
\ ' '..get(b:, 'pandoc_compiler_args', get(g:, 'pandoc_compiler_args', '')) .
\ ' --output %:r:S.$* -- %:S', ' ')
\ ' --standalone'..
\ (s:PandocFiletype(&filetype) ==# 'markdown' && (getline(1) =~# '^%\s\+\S\+' || (search('^title:\s+\S+', 'cnw') > 0)) ?
\ '' : ' --metadata title=%:t:r:S')..
\ ' '..s:PandocLang()..
\ ' --from='..s:PandocFiletype(&filetype)..
\ ' '..get(b:, 'pandoc_compiler_args', get(g:, 'pandoc_compiler_args', ''))..
\ ' --output %:r:S.$* -- %:S', ' ')
CompilerSet errorformat=\"%f\",\ line\ %l:\ %m
let &cpo = s:keepcpo

View File

@ -6201,6 +6201,9 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) *search()*
The value must not be negative. A zero value is like not
giving the argument.
Note: the timeout is only considered when searching, not
while evaluating the {skip} expression.
If the {skip} expression is given it is evaluated with the
cursor positioned on the start of a match. If it evaluates to
non-zero this match is skipped. This can be used, for

View File

@ -189,7 +189,8 @@ TREESITTER
TUI
• TODO
• |log| messages written by the builtin UI client (TUI, |--remote-ui|) are
now prefixed with "ui" instead of "?".
UI

View File

@ -6713,7 +6713,7 @@ A jump table for the options with a short description can be found at |Q_op|.
global
When on, the title of the window will be set to the value of
'titlestring' (if it is not empty), or to:
filename [+=-] (path) - NVIM
filename [+=-] (path) - Nvim
Where:
filename the name of the file being edited
- indicates the file cannot be modified, 'ma' off
@ -6721,7 +6721,7 @@ A jump table for the options with a short description can be found at |Q_op|.
= indicates the file is read-only
=+ indicates the file is read-only and modified
(path) is the path of the file being edited
- NVIM the server name |v:servername| or "NVIM"
- Nvim the server name |v:servername| or "Nvim"
*'titlelen'*
'titlelen' number (default 85)

View File

@ -1262,6 +1262,7 @@ not "b:current_compiler". What the command actually does is the following:
For writing a compiler plugin, see |write-compiler-plugin|.
Use the |compiler-make| plugin to undo the effect of a compiler plugin.
DOTNET *compiler-dotnet*
@ -1277,7 +1278,6 @@ Example: limit output to only display errors, and suppress the project name: >
let dotnet_show_project_file = v:false
compiler dotnet
<
GCC *quickfix-gcc* *compiler-gcc*
There's one variable you can set for the GCC compiler:
@ -1294,6 +1294,24 @@ Commonly used compiler options can be added to 'makeprg' by setting the
g:javac_makeprg_params variable. For example: >
let g:javac_makeprg_params = "-Xlint:all -encoding utf-8"
<
GNU MAKE *compiler-make*
Since the default make program is "make", the compiler plugin for make,
:compiler make, will reset the 'makeprg' and 'errorformat' option to
the default values and unlet any variables that may have been set by a
previous compiler plugin.
GROFF *quickfix-groff* *compiler-groff*
The GROFF compiler plugin uses the mom macro set (documented in the groff_mom
manpage) as input and expects that the output file type extension is passed to
make, say :make html or :make pdf.
Additional arguments can be passed to groff by setting them in
`b:groff_compiler_args` or `g:groff_compiler_args`. The `language` argument
passed to groff is set using 'spelllang'; it can be overridden by setting
`b:groff_compiler_lang`. The default enconding is `UTF-8` and can be changed
by setting `b:groff_compiler_encoding` or `g:groff_compiler_encoding`.
PANDOC *quickfix-pandoc* *compiler-pandoc*
@ -1307,8 +1325,7 @@ Additional arguments can be passed to pandoc:
The `--from` argument is an educated guess using the buffer file type;
it can be overridden by setting `b:pandoc_compiler_from`.
Likewise the `--metadata lang` argument is set using `&spelllang`;
it can be overridden by setting `b:pandoc_compiler_lang`.
The `--metadata lang` argument is set using 'spelllang';
If `--from=markdown` is assumed and no title set in a title header or
YAML block, then the filename (without extension) is used as the title.

3
runtime/ftplugin/dot.lua Normal file
View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -2,8 +2,9 @@
" Filename: spec.vim
" Maintainer: Igor Gnatenko i.gnatenko.brain@gmail.com
" Former Maintainer: Gustavo Niemeyer <niemeyer@conectiva.com> (until March 2014)
" Last Change: Mon Jun 01 21:15 MSK 2015 Igor Gnatenko
" Update by Zdenek Dohnal, 2022 May 17
" Last Change: 2015 Jun 01
" Update by Zdenek Dohnal, 2022 May 17
" 2024 Sep 10 by Vim Project: add epoch support for spec changelog, #15537
if exists("b:did_ftplugin")
finish
@ -66,9 +67,11 @@ if !exists("*s:SpecChangelog")
endif
let line = 0
let name = ""
let epoch = ""
let ver = ""
let rel = ""
let nameline = -1
let epochline = -1
let verline = -1
let relline = -1
let chgline = -1
@ -77,6 +80,9 @@ if !exists("*s:SpecChangelog")
if name == "" && linestr =~? '^Name:'
let nameline = line
let name = substitute(strpart(linestr,5), '^[ ]*\([^ ]\+\)[ ]*$','\1','')
elseif epoch == "" && linestr =~? '^Epoch:'
let epochline = line
let epoch = substitute(strpart(linestr,6), '^[ ]*\([^ ]\+\)[ ]*$','\1','')
elseif ver == "" && linestr =~? '^Version:'
let verline = line
let ver = substitute(strpart(linestr,8), '^[ ]*\([^ ]\+\)[ ]*$','\1','')
@ -93,6 +99,7 @@ if !exists("*s:SpecChangelog")
if nameline != -1 && verline != -1 && relline != -1
let include_release_info = exists("g:spec_chglog_release_info")
let name = s:ParseRpmVars(name, nameline)
let epoch = s:ParseRpmVars(epoch, epochline)
let ver = s:ParseRpmVars(ver, verline)
let rel = s:ParseRpmVars(rel, relline)
else
@ -117,6 +124,9 @@ if !exists("*s:SpecChangelog")
if chgline != -1
let tmptime = v:lc_time
language time C
if strlen(epoch)
let ver = epoch.":".ver
endif
let parsed_format = "* ".strftime(format)." - ".ver."-".rel
execute "language time" tmptime
let release_info = "+ ".name."-".ver."-".rel

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | setl commentstring<'

View File

@ -9,8 +9,8 @@
local function get_commentstring(ref_position)
local buf_cs = vim.bo.commentstring
local has_ts_parser, ts_parser = pcall(vim.treesitter.get_parser)
if not has_ts_parser then
local ts_parser = vim.treesitter._get_parser()
if not ts_parser then
return buf_cs
end

View File

@ -7255,7 +7255,7 @@ vim.go.tm = vim.go.timeoutlen
--- When on, the title of the window will be set to the value of
--- 'titlestring' (if it is not empty), or to:
--- filename [+=-] (path) - NVIM
--- filename [+=-] (path) - Nvim
--- Where:
--- filename the name of the file being edited
--- - indicates the file cannot be modified, 'ma' off
@ -7263,7 +7263,7 @@ vim.go.tm = vim.go.timeoutlen
--- = indicates the file is read-only
--- =+ indicates the file is read-only and modified
--- (path) is the path of the file being edited
--- - NVIM the server name `v:servername` or "NVIM"
--- - Nvim the server name `v:servername` or "Nvim"
---
--- @type boolean
vim.o.title = false

View File

@ -7421,6 +7421,9 @@ function vim.fn.screenstring(row, col) end
--- The value must not be negative. A zero value is like not
--- giving the argument.
---
--- Note: the timeout is only considered when searching, not
--- while evaluating the {skip} expression.
---
--- If the {skip} expression is given it is evaluated with the
--- cursor positioned on the start of a match. If it evaluates to
--- non-zero this match is skipped. This can be used, for

View File

@ -1164,6 +1164,7 @@ local extension = {
svelte = 'svelte',
svg = 'svg',
swift = 'swift',
swiftinterface = 'swift',
swig = 'swig',
swg = 'swig',
sys = detect.sys,

View File

@ -285,8 +285,8 @@ local path2name = function(path)
-- Remove everything up to the last /lua/ folder
path = path:gsub('^.*/lua/', '')
-- Remove the filename (health.lua)
path = vim.fs.dirname(path)
-- Remove the filename (health.lua) or (health/init.lua)
path = vim.fs.dirname(path:gsub('/init%.lua$', ''))
-- Change slashes to dots
path = path:gsub('/', '.')

View File

@ -447,11 +447,9 @@ function M.document_symbol(opts)
request_with_opts(ms.textDocument_documentSymbol, params, opts)
end
--- @param call_hierarchy_items lsp.CallHierarchyItem[]?
--- @param call_hierarchy_items lsp.CallHierarchyItem[]
--- @return lsp.CallHierarchyItem?
local function pick_call_hierarchy_item(call_hierarchy_items)
if not call_hierarchy_items then
return
end
if #call_hierarchy_items == 1 then
return call_hierarchy_items[1]
end
@ -476,7 +474,7 @@ local function call_hierarchy(method)
vim.notify(err.message, vim.log.levels.WARN)
return
end
if not result then
if not result or vim.tbl_isempty(result) then
vim.notify('No item resolved', vim.log.levels.WARN)
return
end

View File

@ -43,17 +43,16 @@ function M.on_inlayhint(err, result, ctx, _)
return
end
local bufnr = assert(ctx.bufnr)
if util.buf_versions[bufnr] ~= ctx.version then
if
util.buf_versions[bufnr] ~= ctx.version
or not result
or not api.nvim_buf_is_loaded(bufnr)
or not bufstates[bufnr].enabled
then
return
end
local client_id = ctx.client_id
if not result then
return
end
local bufstate = bufstates[bufnr]
if not bufstate.enabled then
return
end
if not (bufstate.client_hints and bufstate.version) then
bufstate.client_hints = vim.defaulttable()
bufstate.version = ctx.version

View File

@ -119,6 +119,7 @@ end
---@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16
---@return integer `encoding` index of `index` in `line`
function M._str_utfindex_enc(line, index, encoding)
local len32, len16 = vim.str_utfindex(line)
if not encoding then
encoding = 'utf-16'
end
@ -129,9 +130,15 @@ function M._str_utfindex_enc(line, index, encoding)
return #line
end
elseif encoding == 'utf-16' then
if not index or index > len16 then
return len16
end
local _, col16 = vim.str_utfindex(line, index)
return col16
elseif encoding == 'utf-32' then
if not index or index > len32 then
return len32
end
local col32, _ = vim.str_utfindex(line, index)
return col32
else
@ -171,8 +178,6 @@ function M._str_byteindex_enc(line, index, encoding)
end
end
local _str_utfindex_enc = M._str_utfindex_enc
--- Replaces text in a range with new text.
---
--- CAUTION: Changes in-place!
@ -1928,7 +1933,7 @@ local function make_position_param(window, offset_encoding)
return { line = 0, character = 0 }
end
col = _str_utfindex_enc(line, col, offset_encoding)
col = M._str_utfindex_enc(line, col, offset_encoding)
return { line = row, character = col }
end
@ -2110,11 +2115,7 @@ function M.character_offset(buf, row, col, offset_encoding)
)
offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding
end
-- If the col is past the EOL, use the line length.
if col > #line then
return _str_utfindex_enc(line, nil, offset_encoding)
end
return _str_utfindex_enc(line, col, offset_encoding)
return M._str_utfindex_enc(line, col, offset_encoding)
end
--- Helper function to return nested values in language server settings

View File

@ -74,14 +74,14 @@ end
--- Returns the parser for a specific buffer and attaches it to the buffer
---
--- If needed, this will create the parser.
--- If needed, this will create the parser. If no parser can be found or created, returns `nil`.
---
---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer)
---@param lang (string|nil) Language of this parser (default: from buffer filetype)
---@param opts (table|nil) Options to pass to the created language tree
---
---@return vim.treesitter.LanguageTree object to use for parsing
function M.get_parser(bufnr, lang, opts)
---@return vim.treesitter.LanguageTree? object to use for parsing, or `nil` if not found
function M._get_parser(bufnr, lang, opts)
opts = opts or {}
if bufnr == nil or bufnr == 0 then
@ -94,18 +94,14 @@ function M.get_parser(bufnr, lang, opts)
if not valid_lang(lang) then
if not parsers[bufnr] then
error(
string.format(
'There is no parser available for buffer %d and one could not be'
.. ' created because lang could not be determined. Either pass lang'
.. ' or set the buffer filetype',
bufnr
)
)
return nil
end
elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
assert(lang, 'lang should be valid')
parsers[bufnr] = M._create_parser(bufnr, lang, opts)
local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts)
if not parser then
return nil
end
parsers[bufnr] = parser
end
parsers[bufnr]:register_cbs(opts.buf_attach_cbs)
@ -113,6 +109,29 @@ function M.get_parser(bufnr, lang, opts)
return parsers[bufnr]
end
--- Returns the parser for a specific buffer and attaches it to the buffer
---
--- If needed, this will create the parser.
---
---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer)
---@param lang (string|nil) Language of this parser (default: from buffer filetype)
---@param opts (table|nil) Options to pass to the created language tree
---
---@return vim.treesitter.LanguageTree object to use for parsing
function M.get_parser(bufnr, lang, opts)
-- TODO(ribru17): Remove _get_parser and move that logic back here once the breaking function
-- signature change is acceptable.
local parser = M._get_parser(bufnr, lang, opts)
if not parser then
vim.notify_once(
'WARNING: vim.treesitter.get_parser will return nil instead of raising an error in Neovim 0.12',
vim.log.levels.WARN
)
error('Parser not found.')
end
return parser
end
--- Returns a string parser
---
---@param str string Text to parse
@ -386,7 +405,7 @@ function M.get_node(opts)
local ts_range = { row, col, row, col }
local root_lang_tree = M.get_parser(bufnr, opts.lang)
local root_lang_tree = M._get_parser(bufnr, opts.lang)
if not root_lang_tree then
return
end
@ -419,7 +438,11 @@ end
---@param lang (string|nil) Language of the parser (default: from buffer filetype)
function M.start(bufnr, lang)
bufnr = bufnr or api.nvim_get_current_buf()
local parser = M.get_parser(bufnr, lang)
local parser = M._get_parser(bufnr, lang)
if not parser then
vim.notify('No parser for the given buffer.', vim.log.levels.WARN)
return
end
M.highlighter.new(parser)
end

View File

@ -114,7 +114,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections)
srow = srow or 0
erow = erow or api.nvim_buf_line_count(bufnr)
local parser = ts.get_parser(bufnr)
local parser = assert(ts._get_parser(bufnr))
parser:parse(parse_injections and { srow, erow } or nil)
@ -392,7 +392,7 @@ function M.foldexpr(lnum)
lnum = lnum or vim.v.lnum
local bufnr = api.nvim_get_current_buf()
local parser = vim.F.npcall(ts.get_parser, bufnr)
local parser = ts._get_parser(bufnr)
if not parser then
return '0'
end

View File

@ -172,7 +172,7 @@ function M.lint(buf, opts)
--- @type (table|nil)
local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang)
local parser = vim.treesitter.get_parser(buf)
local parser = assert(vim.treesitter._get_parser(buf), 'query parser not found.')
parser:parse()
parser:for_each_tree(function(tree, ltree)
if ltree:lang() == 'query' then

View File

@ -76,10 +76,9 @@ end
---
---@package
function TSTreeView:new(bufnr, lang)
local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
if not ok then
local err = parser --[[ @as string ]]
return nil, 'No parser available for the given buffer:\n' .. err
local parser = vim.treesitter._get_parser(bufnr or 0, lang)
if not parser then
return nil, 'No parser available for the given buffer.'
end
-- For each child tree (injected language), find the root of the tree and locate the node within
@ -539,7 +538,7 @@ local edit_ns = api.nvim_create_namespace('treesitter/dev-edit')
local function update_editor_highlights(query_win, base_win, lang)
local base_buf = api.nvim_win_get_buf(base_win)
local query_buf = api.nvim_win_get_buf(query_win)
local parser = vim.treesitter.get_parser(base_buf, lang)
local parser = assert(vim.treesitter._get_parser(base_buf, lang))
api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1)
local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n')
@ -596,8 +595,8 @@ function M.edit_query(lang)
end
vim.cmd(cmd)
local ok, parser = pcall(vim.treesitter.get_parser, buf, lang)
if not ok then
local parser = vim.treesitter._get_parser(buf, lang)
if not parser then
return nil, 'No parser available for the given buffer'
end
lang = parser:lang()

View File

@ -33,7 +33,7 @@ end
--- Show a table of contents for the help buffer in a loclist
function M.show_toc()
local bufnr = vim.api.nvim_get_current_buf()
local parser = vim.treesitter.get_parser(bufnr, 'vimdoc')
local parser = assert(vim.treesitter._get_parser(bufnr, 'vimdoc'), 'vimdoc parser not found.')
local query = vim.treesitter.query.parse(
parser:lang(),
[[

View File

@ -45,6 +45,11 @@
(link_destination) @_url
(#set! @_label url @_url))
(image
(image_description) @_label
(link_destination) @_url
(#set! @_label url @_url))
; Conceal image links
(image
[

View File

@ -33,7 +33,10 @@
")"
] @punctuation.bracket
":" @punctuation.delimiter
[
":"
"/"
] @punctuation.delimiter
[
"@"

View File

@ -1,12 +1,12 @@
" Vim syntax file
" Language: Configuration File (ini file) for MSDOS/MS Windows
" Version: 2.3
" Version: 2.4
" Original Author: Sean M. McKee <mckee@misslink.net>
" Previous Maintainer: Nima Talebi <nima@it.net.au>
" Current Maintainer: Hong Xu <hong@topbug.net>
" Homepage: http://www.vim.org/scripts/script.php?script_id=3747
" Repository: https://github.com/xuhdev/syntax-dosini.vim
" Last Change: 2023 Aug 20
" Last Change: 2024 Sept 08
" quit when a syntax file was already loaded
@ -27,7 +27,7 @@ syn match dosiniNumber "=\zs\s*\d\+\s*$"
syn match dosiniNumber "=\zs\s*\d*\.\d\+\s*$"
syn match dosiniNumber "=\zs\s*\d\+e[+-]\=\d\+\s*$"
syn region dosiniHeader start="^\s*\[" end="\]"
syn match dosiniComment "^[#;].*$"
syn match dosiniComment "^[#;].*$" contains=@Spell
syn region dosiniSection start="\s*\[.*\]" end="\ze\s*\[.*\]" fold
\ contains=dosiniLabel,dosiniValue,dosiniNumber,dosiniHeader,dosiniComment

View File

@ -1,7 +1,8 @@
" Interactive Data Language syntax file (IDL, too [:-)]
" Maintainer: Aleksandar Jelenak <ajelenak AT yahoo.com>
" Last change: 2011 Apr 11
" Created by: Hermann Rochholz <Hermann.Rochholz AT gmx.de>
" Created By: Hermann Rochholz <Hermann.Rochholz AT gmx.de>
" Last Change: 2011 Apr 11
" 2024 Sep 10 by Vim Project: update syntax script, #15419
" Remove any old syntax stuff hanging around
" quit when a syntax file was already loaded
@ -16,7 +17,7 @@ syn match idlangStatement "^\s*function\s"
syn keyword idlangStatement return continue mod do break
syn keyword idlangStatement compile_opt forward_function goto
syn keyword idlangStatement begin common end of
syn keyword idlangStatement inherits on_ioerror begin
syn keyword idlangStatement inherits on_error on_ioerror begin
syn keyword idlangConditional if else then for while case switch
syn keyword idlangConditional endcase endelse endfor endswitch
@ -82,7 +83,7 @@ syn keyword idlangRoutine CALL_EXTERNAL CALL_FUNCTION CALL_METHOD
syn keyword idlangRoutine CALL_PROCEDURE CATCH CD CEIL CHEBYSHEV CHECK_MATH
syn keyword idlangRoutine CHISQR_CVF CHISQR_PDF CHOLDC CHOLSOL CINDGEN
syn keyword idlangRoutine CIR_3PNT CLOSE CLUST_WTS CLUSTER COLOR_CONVERT
syn keyword idlangRoutine COLOR_QUAN COLORMAP_APPLICABLE COMFIT COMMON
syn keyword idlangRoutine COLOR_QUAN COLORMAP_APPLICABLE COMFIT
syn keyword idlangRoutine COMPLEX COMPLEXARR COMPLEXROUND
syn keyword idlangRoutine COMPUTE_MESH_NORMALS COND CONGRID CONJ
syn keyword idlangRoutine CONSTRAINED_MIN CONTOUR CONVERT_COORD CONVOL
@ -98,7 +99,7 @@ syn keyword idlangRoutine CW_PALETTE_EDITOR_GET CW_PALETTE_EDITOR_SET
syn keyword idlangRoutine CW_PDMENU CW_RGBSLIDER CW_TMPL CW_ZOOM DBLARR
syn keyword idlangRoutine DCINDGEN DCOMPLEX DCOMPLEXARR DEFINE_KEY DEFROI
syn keyword idlangRoutine DEFSYSV DELETE_SYMBOL DELLOG DELVAR DERIV DERIVSIG
syn keyword idlangRoutine DETERM DEVICE DFPMIN DIALOG_MESSAGE
syn keyword idlangRoutine DETERM DEVICE DFPMIN DIAG_MATRIX DIALOG_MESSAGE
syn keyword idlangRoutine DIALOG_PICKFILE DIALOG_PRINTERSETUP
syn keyword idlangRoutine DIALOG_PRINTJOB DIALOG_READ_IMAGE
syn keyword idlangRoutine DIALOG_WRITE_IMAGE DIGITAL_FILTER DILATE DINDGEN
@ -155,7 +156,7 @@ syn keyword idlangRoutine MPEG_PUT MPEG_SAVE MSG_CAT_CLOSE MSG_CAT_COMPILE
syn keyword idlangRoutine MSG_CAT_OPEN MULTI N_ELEMENTS N_PARAMS N_TAGS
syn keyword idlangRoutine NEWTON NORM OBJ_CLASS OBJ_DESTROY OBJ_ISA OBJ_NEW
syn keyword idlangRoutine OBJ_VALID OBJARR ON_ERROR ON_IOERROR ONLINE_HELP
syn keyword idlangRoutine OPEN OPENR OPENW OPLOT OPLOTERR P_CORRELATE
syn keyword idlangRoutine OPEN OPENR OPENW OPENU OPLOT OPLOTERR P_CORRELATE
syn keyword idlangRoutine PARTICLE_TRACE PCOMP PLOT PLOT_3DBOX PLOT_FIELD
syn keyword idlangRoutine PLOTERR PLOTS PNT_LINE POINT_LUN POLAR_CONTOUR
syn keyword idlangRoutine POLAR_SURFACE POLY POLY_2D POLY_AREA POLY_FIT

View File

@ -3,7 +3,7 @@
" Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com>
" Former Maintainer: Claudio Fleiner <claudio@fleiner.com>
" Repository: https://github.com/zzzyxwvut/java-vim.git
" Last Change: 2024 Aug 26
" Last Change: 2024 Sep 10
" Please check :help java.vim for comments on some of the options available.
@ -157,13 +157,20 @@ endif
" testing in a project without attendant confusion for IDEs, with the
" ".java\=" extension used for a production version and an arbitrary
" extension used for a testing version.
if fnamemodify(bufname("%"), ":t") =~ '^module-info\%(\.class\>\)\@!'
if fnamemodify(bufname("%"), ":t") =~ '^module-info\>\%(\.class\>\)\@!'
syn keyword javaModuleStorageClass module transitive
syn keyword javaModuleStmt open requires exports opens uses provides
syn keyword javaModuleExternal to with
hi def link javaModuleStorageClass StorageClass
hi def link javaModuleStmt Statement
hi def link javaModuleExternal Include
if !exists("g:java_ignore_javadoc") && g:main_syntax != 'jsp'
syn match javaDocProvidesTag contained "@provides\_s\+\S\+" contains=javaDocParam
syn match javaDocUsesTag contained "@uses\_s\+\S\+" contains=javaDocParam
hi def link javaDocProvidesTag Special
hi def link javaDocUsesTag Special
endif
endif
" Fancy parameterised types (JLS-17, §4.5).
@ -335,29 +342,99 @@ if !exists("g:java_ignore_javadoc") && g:main_syntax != 'jsp'
call s:ReportOnce(v:exception)
endtry
syn region javaDocComment start="/\*\*" end="\*/" keepend contains=javaCommentTitle,@javaHtml,javaDocTags,javaDocSeeTag,javaDocCodeTag,javaDocSnippetTag,javaTodo,javaCommentError,javaSpaceError,@Spell fold
exec 'syn region javaCommentTitle contained matchgroup=javaDocComment start="/\*\*" matchgroup=javaCommentTitle end="\.$" end="\.[ \t\r]\@=" end="\%(^\s*\**\s*\)\@' . s:ff.Peek('80', '') . '<=@"me=s-2,he=s-1 end="\*/"me=s-1,he=s-1 contains=@javaHtml,javaCommentStar,javaTodo,javaCommentError,javaSpaceError,@Spell,javaDocTags,javaDocSeeTag,javaDocCodeTag,javaDocSnippetTag'
syn region javaCommentTitle contained matchgroup=javaDocComment start="/\*\*\s*\r\=\n\=\s*\**\s*\%({@return\>\)\@=" matchgroup=javaCommentTitle end="}\%(\s*\.*\)*" contains=@javaHtml,javaCommentStar,javaTodo,javaCommentError,javaSpaceError,@Spell,javaDocTags,javaDocSeeTag,javaDocCodeTag,javaDocSnippetTag
syn region javaDocTags contained start="{@\%(li\%(teral\|nk\%(plain\)\=\)\|inherit[Dd]oc\|doc[rR]oot\|value\)\>" end="}"
syn match javaDocTags contained "@\%(param\|exception\|throws\|since\)\s\+\S\+" contains=javaDocParam
syn match javaDocParam contained "\s\S\+"
syn match javaDocTags contained "@\%(version\|author\|return\|deprecated\|serial\%(Field\|Data\)\=\)\>"
syn region javaDocSeeTag contained matchgroup=javaDocTags start="@see\s\+" matchgroup=NONE end="\_."re=e-1 contains=javaDocSeeTagParam
syn match javaDocSeeTagParam contained @"\_[^"]\+"\|<a\s\+\_.\{-}</a>\|\%(\k\|\.\)*\%(#\k\+\%((\_[^)]*)\)\=\)\=@ contains=@javaHtml extend
syn region javaDocComment start="/\*\*" end="\*/" keepend contains=javaCommentTitle,@javaHtml,@javaDocTags,javaTodo,javaCommentError,javaSpaceError,@Spell fold
exec 'syn region javaCommentTitle contained matchgroup=javaDocComment start="/\*\*" matchgroup=javaCommentTitle end="\.$" end="\.[ \t\r]\@=" end="\%(^\s*\**\s*\)\@' . s:ff.Peek('80', '') . '<=@"me=s-2,he=s-1 end="\*/"me=s-1,he=s-1 contains=@javaHtml,javaCommentStar,javaTodo,javaCommentError,javaSpaceError,@Spell,@javaDocTags'
syn region javaCommentTitle contained matchgroup=javaDocComment start="/\*\*\s*\r\=\n\=\s*\**\s*\%({@return\>\)\@=" matchgroup=javaCommentTitle end="}\%(\s*\.*\)*" contains=@javaHtml,javaCommentStar,javaTodo,javaCommentError,javaSpaceError,@Spell,@javaDocTags,javaTitleSkipBlock
syn region javaCommentTitle contained matchgroup=javaDocComment start="/\*\*\s*\r\=\n\=\s*\**\s*\%({@summary\>\)\@=" matchgroup=javaCommentTitle end="}" contains=@javaHtml,javaCommentStar,javaTodo,javaCommentError,javaSpaceError,@Spell,@javaDocTags,javaTitleSkipBlock
" The members of javaDocTags are sub-grouped according to the Java
" version of their introduction, and sub-group members in turn are
" arranged in alphabetical order, so that future newer members can
" be pre-sorted and appended without disturbing the current member
" placement.
" Since they only have significance in javaCommentTitle, neither
" javaDocSummaryTag nor javaDocReturnTitleTag are defined.
syn cluster javaDocTags contains=javaDocAuthorTag,javaDocDeprecatedTag,javaDocExceptionTag,javaDocParamTag,javaDocReturnTag,javaDocSeeTag,javaDocVersionTag,javaDocSinceTag,javaDocLinkTag,javaDocSerialTag,javaDocSerialDataTag,javaDocSerialFieldTag,javaDocThrowsTag,javaDocDocRootTag,javaDocInheritDocTag,javaDocLinkplainTag,javaDocValueTag,javaDocCodeTag,javaDocLiteralTag,javaDocHiddenTag,javaDocIndexTag,javaDocProvidesTag,javaDocUsesTag,javaDocSystemPropertyTag,javaDocSnippetTag,javaDocSpecTag
" Anticipate non-standard inline tags in {@return} and {@summary}.
syn region javaTitleSkipBlock contained transparent start="{\%(@\%(return\|summary\)\>\)\@!" end="}"
syn match javaDocDocRootTag contained "{@docRoot}"
syn match javaDocInheritDocTag contained "{@inheritDoc}"
syn region javaIndexSkipBlock contained transparent start="{\%(@index\>\)\@!" end="}" contains=javaIndexSkipBlock,javaDocIndexTag
syn region javaDocIndexTag contained start="{@index\>" end="}" contains=javaDocIndexTag,javaIndexSkipBlock
syn region javaLinkSkipBlock contained transparent start="{\%(@link\>\)\@!" end="}" contains=javaLinkSkipBlock,javaDocLinkTag
syn region javaDocLinkTag contained start="{@link\>" end="}" contains=javaDocLinkTag,javaLinkSkipBlock
syn region javaLinkplainSkipBlock contained transparent start="{\%(@linkplain\>\)\@!" end="}" contains=javaLinkplainSkipBlock,javaDocLinkplainTag
syn region javaDocLinkplainTag contained start="{@linkplain\>" end="}" contains=javaDocLinkplainTag,javaLinkplainSkipBlock
syn region javaLiteralSkipBlock contained transparent start="{\%(@literal\>\)\@!" end="}" contains=javaLiteralSkipBlock,javaDocLiteralTag
syn region javaDocLiteralTag contained start="{@literal\>" end="}" contains=javaDocLiteralTag,javaLiteralSkipBlock
syn region javaSystemPropertySkipBlock contained transparent start="{\%(@systemProperty\>\)\@!" end="}" contains=javaSystemPropertySkipBlock,javaDocSystemPropertyTag
syn region javaDocSystemPropertyTag contained start="{@systemProperty\>" end="}" contains=javaDocSystemPropertyTag,javaSystemPropertySkipBlock
syn region javaValueSkipBlock contained transparent start="{\%(@value\>\)\@!" end="}" contains=javaValueSkipBlock,javaDocValueTag
syn region javaDocValueTag contained start="{@value\>" end="}" contains=javaDocValueTag,javaValueSkipBlock
syn match javaDocParam contained "\s\zs\S\+"
syn match javaDocExceptionTag contained "@exception\s\+\S\+" contains=javaDocParam
syn match javaDocParamTag contained "@param\s\+\S\+" contains=javaDocParam
syn match javaDocSinceTag contained "@since\s\+\S\+" contains=javaDocParam
syn match javaDocThrowsTag contained "@throws\s\+\S\+" contains=javaDocParam
syn match javaDocSpecTag contained "@spec\_s\+\S\+\ze\_s\+\S\+" contains=javaDocParam
syn match javaDocAuthorTag contained "@author\>"
syn match javaDocDeprecatedTag contained "@deprecated\>"
syn match javaDocHiddenTag contained "@hidden\>"
syn match javaDocReturnTag contained "@return\>"
syn match javaDocSerialTag contained "@serial\>"
syn match javaDocSerialDataTag contained "@serialData\>"
syn match javaDocSerialFieldTag contained "@serialField\>"
syn match javaDocVersionTag contained "@version\>"
syn match javaDocSeeTag contained "@see\>" nextgroup=javaDocSeeTag1,javaDocSeeTag2,javaDocSeeTag3,javaDocSeeTagStar skipwhite skipempty
syn match javaDocSeeTagStar contained "^\s*\*\+\%(\s*{\=@\|/\|$\)\@!" nextgroup=javaDocSeeTag1,javaDocSeeTag2,javaDocSeeTag3 skipwhite skipempty
syn match javaDocSeeTag1 contained @"\_[^"]\+"@
syn match javaDocSeeTag2 contained @<a\s\+\_.\{-}</a>@ contains=@javaHtml extend
syn match javaDocSeeTag3 contained @["< \t]\@!\%(\k\|[/.]\)*\%(##\=\k\+\%((\_[^)]*)\)\=\)\=@ nextgroup=javaDocSeeTag3Label skipwhite skipempty
syn match javaDocSeeTag3Label contained @\k\%(\k\+\s*\)*$@
syn region javaCodeSkipBlock contained transparent start="{\%(@code\>\)\@!" end="}" contains=javaCodeSkipBlock,javaDocCodeTag
syn region javaDocCodeTag contained start="{@code\>" end="}" contains=javaDocCodeTag,javaCodeSkipBlock
exec 'syn region javaDocSnippetTagAttr contained transparent matchgroup=javaHtmlArg start=/\<\%(class\|file\|id\|lang\|region\)\%(\s*=\)\@=/ matchgroup=javaHtmlString end=/:$/ end=/\%(=\s*\)\@' . s:ff.Peek('80', '') . '<=\%("[^"]\+"\|' . "\x27[^\x27]\\+\x27" . '\|\%([.\\/-]\|\k\)\+\)/ nextgroup=javaDocSnippetTagAttr skipwhite skipnl'
syn region javaSnippetSkipBlock contained transparent start="{\%(@snippet\>\)\@!" end="}" contains=javaSnippetSkipBlock,javaDocSnippetTag,javaCommentMarkupTag
syn region javaDocSnippetTag contained start="{@snippet\>" end="}" contains=javaDocSnippetTag,javaSnippetSkipBlock,javaDocSnippetTagAttr,javaCommentMarkupTag
syntax case match
hi def link javaDocComment Comment
hi def link javaDocSeeTagStar javaDocComment
hi def link javaCommentTitle SpecialComment
hi def link javaDocTags Special
hi def link javaDocCodeTag Special
hi def link javaDocSnippetTag Special
hi def link javaDocSeeTagParam Function
hi def link javaDocParam Function
hi def link javaDocAuthorTag Special
hi def link javaDocCodeTag Special
hi def link javaDocDeprecatedTag Special
hi def link javaDocDocRootTag Special
hi def link javaDocExceptionTag Special
hi def link javaDocHiddenTag Special
hi def link javaDocIndexTag Special
hi def link javaDocInheritDocTag Special
hi def link javaDocLinkTag Special
hi def link javaDocLinkplainTag Special
hi def link javaDocLiteralTag Special
hi def link javaDocParamTag Special
hi def link javaDocReturnTag Special
hi def link javaDocSeeTag Special
hi def link javaDocSeeTag1 String
hi def link javaDocSeeTag2 Special
hi def link javaDocSeeTag3 Function
hi def link javaDocSerialTag Special
hi def link javaDocSerialDataTag Special
hi def link javaDocSerialFieldTag Special
hi def link javaDocSinceTag Special
hi def link javaDocSnippetTag Special
hi def link javaDocSpecTag Special
hi def link javaDocSystemPropertyTag Special
hi def link javaDocThrowsTag Special
hi def link javaDocValueTag Special
hi def link javaDocVersionTag Special
endif
" match the special comment /**/

View File

@ -4,6 +4,7 @@
" Maintainer: Igor Gnatenko i.gnatenko.brain@gmail.com
" Former Maintainer: Donovan Rebbechi elflord@panix.com (until March 2014)
" Last Change: 2020 May 25
" 2024 Sep 10 by Vim Project: add file triggers support, #15569
" quit when a syntax file was already loaded
if exists("b:current_syntax")
@ -111,7 +112,7 @@ syn region specDescriptionArea matchgroup=specSection start='^%description' end=
syn region specPackageArea matchgroup=specSection start='^%package' end='^%'me=e-1 contains=specPackageOpts,specPreAmble,specComment
"%% Scripts Section %%
syn region specScriptArea matchgroup=specSection start='^%\(prep\|generate_buildrequires\|conf\|build\|install\|clean\|check\|pre\|postun\|preun\|post\|posttrans\)\>' skip='^%{\|^%\(define\|patch\d*\|configure\|GNUconfigure\|setup\|autosetup\|autopatch\|find_lang\|make_build\|makeinstall\|make_install\)\>' end='^%'me=e-1 contains=specSpecialVariables,specVariables,@specCommands,specVariables,shDo,shFor,shCaseEsac,specNoNumberHilite,specCommandOpts,shComment,shIf,specSpecialChar,specMacroIdentifier,specSectionMacroArea,specSectionMacroBracketArea,shOperator,shQuote1,shQuote2
syn region specScriptArea matchgroup=specSection start='^%\(prep\|generate_buildrequires\|conf\|build\|install\|clean\|check\|pre\|postun\|preun\|post\|posttrans\|filetriggerin\|filetriggerun\|filetriggerpostun\|transfiletriggerin\|transfiletriggerun\|transfiletriggerpostun\)\>' skip='^%{\|^%\(define\|patch\d*\|configure\|GNUconfigure\|setup\|autosetup\|autopatch\|find_lang\|make_build\|makeinstall\|make_install\)\>' end='^%'me=e-1 contains=specSpecialVariables,specVariables,@specCommands,specVariables,shDo,shFor,shCaseEsac,specNoNumberHilite,specCommandOpts,shComment,shIf,specSpecialChar,specMacroIdentifier,specSectionMacroArea,specSectionMacroBracketArea,shOperator,shQuote1,shQuote2
"%% Changelog Section %%
syn region specChangelogArea matchgroup=specSection start='^%changelog' end='^%'me=e-1 contains=specEmail,specURL,specWeekday,specMonth,specNumber,specComment,specLicense

View File

@ -81,6 +81,14 @@ local function get_dependency(dependency_name)
repo = 'luvit/luv',
symbol = 'LUV',
},
['unibilium'] = {
repo = 'neovim/unibilium',
symbol = 'UNIBILIUM',
},
['utf8proc'] = {
repo = 'JuliaStrings/utf8proc',
symbol = 'UTF8PROC',
},
['tree-sitter'] = {
repo = 'tree-sitter/tree-sitter',
symbol = 'TREESITTER',
@ -90,11 +98,11 @@ local function get_dependency(dependency_name)
symbol = 'TREESITTER_C',
},
['tree-sitter-lua'] = {
repo = 'MunifTanjim/tree-sitter-lua',
repo = 'tree-sitter-grammars/tree-sitter-lua',
symbol = 'TREESITTER_LUA',
},
['tree-sitter-vim'] = {
repo = 'neovim/tree-sitter-vim',
repo = 'tree-sitter-grammars/tree-sitter-vim',
symbol = 'TREESITTER_VIM',
},
['tree-sitter-vimdoc'] = {
@ -102,9 +110,21 @@ local function get_dependency(dependency_name)
symbol = 'TREESITTER_VIMDOC',
},
['tree-sitter-query'] = {
repo = 'nvim-treesitter/tree-sitter-query',
repo = 'tree-sitter-grammars/tree-sitter-query',
symbol = 'TREESITTER_QUERY',
},
['tree-sitter-markdown'] = {
repo = 'tree-sitter-grammars/tree-sitter-markdown',
symbol = 'TREESITTER_MARKDOWN',
},
['wasmtime'] = {
repo = 'bytecodealliance/wasmtime',
symbol = 'WASMTIME',
},
['uncrustify'] = {
repo = 'uncrustify/uncrustify',
symbol = 'UNCRUSTIFY',
},
}
local dependency = dependency_table[dependency_name]
if dependency == nil then

View File

@ -786,7 +786,7 @@ local function parse_buf(fname, parser_path)
if parser_path then
vim.treesitter.language.add('vimdoc', { path = parser_path })
end
local lang_tree = vim.treesitter.get_parser(buf)
local lang_tree = assert(vim.treesitter._get_parser(buf), 'vimdoc parser not found.')
return lang_tree, buf
end

View File

@ -3338,7 +3338,7 @@ void maketitle(void)
#define SPACE_FOR_FNAME (sizeof(buf) - 100)
#define SPACE_FOR_DIR (sizeof(buf) - 20)
#define SPACE_FOR_ARGNR (sizeof(buf) - 10) // At least room for " - NVIM".
#define SPACE_FOR_ARGNR (sizeof(buf) - 10) // At least room for " - Nvim".
char *buf_p = buf;
if (curbuf->b_fname == NULL) {
const size_t size = xstrlcpy(buf_p, _("[No Name]"),
@ -3412,7 +3412,7 @@ void maketitle(void)
append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)));
xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf)));
xstrlcat(buf_p, " - Nvim", (sizeof(buf) - (size_t)(buf_p - buf)));
if (maxlen > 0) {
// Make it shorter by removing a bit in the middle.

View File

@ -922,15 +922,15 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
for (int i = 0; i < xp->xp_numfiles; i++) {
if (i > 0) {
if (xp->xp_prefix == XP_PREFIX_NO) {
ssp = xstpcpy(ssp, "no");
ssp = STPCPY(ssp, "no");
} else if (xp->xp_prefix == XP_PREFIX_INV) {
ssp = xstpcpy(ssp, "inv");
ssp = STPCPY(ssp, "inv");
}
}
ssp = xstpcpy(ssp, xp->xp_files[i]);
ssp = STPCPY(ssp, xp->xp_files[i]);
if (i != xp->xp_numfiles - 1) {
ssp = xstpcpy(ssp, (options & WILD_USE_NL) ? "\n" : " ");
ssp = STPCPY(ssp, (options & WILD_USE_NL) ? "\n" : " ");
}
}
}

View File

@ -8927,6 +8927,9 @@ M.funcs = {
The value must not be negative. A zero value is like not
giving the argument.
Note: the timeout is only considered when searching, not
while evaluating the {skip} expression.
If the {skip} expression is given it is evaluated with the
cursor positioned on the start of a match. If it evaluates to
non-zero this match is skipped. This can be used, for

View File

@ -16,15 +16,14 @@ struct loop {
uv_loop_t uv;
MultiQueue *events;
MultiQueue *thread_events;
// Immediate events:
// "Processed after exiting uv_run() (to avoid recursion), but before
// returning from loop_poll_events()." 502aee690c98
// Practical consequence (for main_loop): these events are processed by
// state_enter()..os_inchar()
// whereas "regular" events (main_loop.events) are processed by
// state_enter()..VimState.execute()
// But state_enter()..os_inchar() can be "too early" if you want the event
// to trigger UI updates and other user-activity-related side-effects.
// Immediate events.
// - "Processed after exiting `uv_run()` (to avoid recursion), but before returning from
// `loop_poll_events()`." 502aee690c98
// - Practical consequence (for `main_loop`):
// - these are processed by `state_enter()..input_get()` whereas "regular" events
// (`main_loop.events`) are processed by `state_enter()..VimState.execute()`
// - `state_enter()..input_get()` can be "too early" if you want the event to trigger UI
// updates and other user-activity-related side-effects.
MultiQueue *fast_events;
// used by process/job-control subsystem

View File

@ -94,14 +94,17 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
stream->events = NULL;
}
void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data, bool rstream)
void stream_may_close(Stream *stream, bool rstream)
FUNC_ATTR_NONNULL_ARG(1)
{
if (stream->closed) {
return;
}
assert(!stream->closed);
DLOG("closing Stream: %p", (void *)stream);
stream->closed = true;
stream->close_cb = on_stream_close;
stream->close_cb_data = data;
stream->close_cb = NULL;
stream->close_cb_data = NULL;
#ifdef MSWIN
if (UV_TTY == uv_guess_handle(stream->fd)) {
@ -115,13 +118,6 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data, b
}
}
void stream_may_close(Stream *stream, bool rstream)
{
if (!stream->closed) {
stream_close(stream, NULL, NULL, rstream);
}
}
void stream_close_handle(Stream *stream, bool rstream)
FUNC_ATTR_NONNULL_ALL
{

View File

@ -152,8 +152,8 @@ char *ga_concat_strings_sep(const garray_T *gap, const char *sep)
char *s = ret;
for (size_t i = 0; i < nelem - 1; i++) {
s = xstpcpy(s, strings[i]);
s = xstpcpy(s, sep);
s = STPCPY(s, strings[i]);
s = STPCPY(s, sep);
}
strcpy(s, strings[nelem - 1]); // NOLINT(runtime/printf)

View File

@ -1858,7 +1858,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
if (!char_avail()) {
// Flush screen updates before blocking.
ui_flush();
os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
state_handle_k_event();
continue;
@ -2981,7 +2981,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time)
uint8_t dum[DUM_LEN + 1];
while (true) {
len = os_inchar(dum, DUM_LEN, 0, 0, NULL);
len = input_get(dum, DUM_LEN, 0, 0, NULL);
if (len == 0 || (len == 1 && dum[0] == Ctrl_C)) {
break;
}
@ -2997,7 +2997,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time)
// Fill up to a third of the buffer, because each character may be
// tripled below.
len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL);
len = input_get(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL);
}
// If the typebuf was changed further down, it is like nothing was added by

View File

@ -370,12 +370,15 @@ void update_window_hl(win_T *wp, bool invalid)
// determine window specific background set in 'winhighlight'
bool float_win = wp->w_floating && !wp->w_config.external;
if (float_win && hl_def[HLF_NFLOAT] != 0) {
if (float_win && hl_def[HLF_NFLOAT] != 0 && ns_id > 0) {
wp->w_hl_attr_normal = hl_def[HLF_NFLOAT];
} else if (hl_def[HLF_COUNT] > 0) {
wp->w_hl_attr_normal = hl_def[HLF_COUNT];
} else if (float_win) {
wp->w_hl_attr_normal = HL_ATTR(HLF_NFLOAT) > 0
? HL_ATTR(HLF_NFLOAT) : highlight_attr[HLF_NFLOAT];
} else {
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
wp->w_hl_attr_normal = 0;
}
if (wp->w_floating) {

View File

@ -37,7 +37,7 @@
/// @param[in] str Prompt: question to ask user. Is always followed by
/// " (y/n)?".
/// @param[in] direct Determines what function to use to get user input. If
/// true then os_inchar() will be used, otherwise vgetc().
/// true then input_get() will be used, otherwise vgetc().
/// I.e. when direct is true then characters are obtained
/// directly from the user without buffers involved.
///
@ -111,7 +111,7 @@ int get_keystroke(MultiQueue *events)
// First time: blocking wait. Second time: wait up to 100ms for a
// terminal code to complete.
n = os_inchar(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
n = input_get(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
if (n > 0) {
// Replace zero and K_SPECIAL by a special key code.
n = fix_input_buffer(buf + len, n);

View File

@ -29,6 +29,7 @@
#include "nvim/os/stdpaths_defs.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/ui_client.h"
/// Cached location of the expanded log file path decided by log_path_init().
static char log_file_path[MAXPATHL + 1] = { 0 };
@ -322,20 +323,28 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
millis = (int)curtime.tv_usec / 1000;
}
bool ui = !!ui_client_channel_id; // Running as a UI client (--remote-ui).
// Regenerate the name when:
// - UI client (to ensure "ui" is in the name)
// - not set yet
// - no v:servername yet
bool regen = ui || name[0] == NUL || name[0] == '?';
// Get a name for this Nvim instance.
// TODO(justinmk): expose this as v:name ?
if (name[0] == NUL) {
// Parent servername.
if (regen) {
// Parent servername ($NVIM).
const char *parent = path_tail(os_getenv(ENV_NVIM));
// Servername. Empty until starting=false.
const char *serv = path_tail(get_vim_var_str(VV_SEND_SERVER));
if (parent[0] != NUL) {
snprintf(name, sizeof(name), "%s/c", parent); // "/c" indicates child.
snprintf(name, sizeof(name), ui ? "ui/c/%s" : "c/%s", parent); // "c/" = child of $NVIM.
} else if (serv[0] != NUL) {
snprintf(name, sizeof(name), "%s", serv);
snprintf(name, sizeof(name), ui ? "ui/%s" : "%s", serv);
} else {
int64_t pid = os_get_pid();
snprintf(name, sizeof(name), "?.%-5" PRId64, pid);
snprintf(name, sizeof(name), "%s.%-5" PRId64, ui ? "ui" : "?", pid);
}
}
@ -348,10 +357,6 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
log_levels[log_level], date_time, millis, name,
(context == NULL ? "" : context),
func_name, line_num);
if (name[0] == '?') {
// No v:servername yet. Clear `name` so that the next log can try again.
name[0] = NUL;
}
if (rv < 0) {
return false;

View File

@ -333,7 +333,7 @@ int ml_open(buf_T *buf)
b0p->b0_magic_int = B0_MAGIC_INT;
b0p->b0_magic_short = (int16_t)B0_MAGIC_SHORT;
b0p->b0_magic_char = B0_MAGIC_CHAR;
xstrlcpy(xstpcpy(b0p->b0_version, "VIM "), Version, 6);
xstrlcpy(STPCPY(b0p->b0_version, "VIM "), Version, 6);
long_to_char((long)mfp->mf_page_size, b0p->b0_page_size);
if (!buf->b_spell) {

View File

@ -348,43 +348,16 @@ size_t memcnt(const void *data, char c, size_t len)
return cnt;
}
/// Copies the string pointed to by src (including the terminating NUL
/// character) into the array pointed to by dst.
///
/// @returns pointer to the terminating NUL char copied into the dst buffer.
/// This is the only difference with strcpy(), which returns dst.
///
/// WARNING: If copying takes place between objects that overlap, the behavior
/// is undefined.
///
/// Nvim version of POSIX 2008 stpcpy(3). We do not require POSIX 2008, so
/// implement our own version.
///
/// @param dst
/// @param src
#ifndef HAVE_STPCPY
char *xstpcpy(char *restrict dst, const char *restrict src)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
const size_t len = strlen(src);
return (char *)memcpy(dst, src, len + 1) + len;
}
#endif
/// Copies not more than n bytes (bytes that follow a NUL character are not
/// copied) from the array pointed to by src to the array pointed to by dst.
///
/// If a NUL character is written to the destination, xstpncpy() returns the
/// address of the first such NUL character. Otherwise, it shall return
/// &dst[maxlen].
///
/// WARNING: If copying takes place between objects that overlap, the behavior
/// is undefined.
///
/// WARNING: xstpncpy will ALWAYS write maxlen bytes. If src is shorter than
/// maxlen, zeroes will be written to the remaining bytes.
///
/// @param dst
/// @param src
/// @param maxlen
#ifndef HAVE_STPNCPY
char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
@ -399,6 +372,7 @@ char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen)
return dst + maxlen;
}
}
#endif
/// xstrlcpy - Copy a NUL-terminated string into a sized buffer
///

View File

@ -66,6 +66,19 @@ EXTERN size_t arena_alloc_count INIT( = 0);
# define strnlen xstrnlen // Older versions of SunOS may not have strnlen
#endif
// MacOS doesn't have HAVE_STPCPY defined and defines its own stpcpy in Xcode. So just define STPCPY
#ifdef HAVE_STPCPY
# define STPCPY stpcpy
#else
# define STPCPY xstpcpy
#endif
#ifdef HAVE_STPNCPY
# define STPNCPY stpncpy
#else
# define STPNCPY xstpncpy
#endif
#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
// Like strcpy() but allows overlapped source and destination.

View File

@ -12,7 +12,7 @@
/// HACK: os/input.c drains this queue immediately before blocking for input.
/// Events on this queue are async-safe, but they need the resolved state
/// of os_inchar(), so they are processed "just-in-time".
/// of input_get(), so they are processed "just-in-time".
EXTERN MultiQueue *ch_before_blocking_events INIT( = NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS

View File

@ -51,13 +51,9 @@ bool server_init(const char *listen_addr)
must_free = true;
}
if (!listen_addr) {
abort(); // Cannot happen.
}
int rv = server_start(listen_addr);
// TODO(justinmk): this is for logging_spec. Can remove this after nvim_log #7062 is merged.
// TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
if (os_env_exists("__NVIM_TEST_LOG")) {
ELOG("test log message");
}
@ -77,7 +73,7 @@ bool server_init(const char *listen_addr)
kTrue ? "Failed to --listen: %s: \"%s\""
: "Failed $NVIM_LISTEN_ADDRESS: %s: \"%s\"",
rv < 0 ? os_strerror(rv) : (rv == 1 ? "empty address" : "?"),
listen_addr ? listen_addr : "(empty)");
listen_addr);
ok = false;
end:
@ -118,6 +114,7 @@ void server_teardown(void)
/// - Windows: "\\.\pipe\<name>.<pid>.<counter>"
/// - Other: "/tmp/nvim.user/xxx/<name>.<pid>.<counter>"
char *server_address_new(const char *name)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
{
static uint32_t count = 0;
char fmt[ADDRESS_MAX_SIZE];

View File

@ -6597,11 +6597,11 @@ static void nv_open(cmdarg_T *cap)
static void nv_event(cmdarg_T *cap)
{
// Garbage collection should have been executed before blocking for events in
// the `os_inchar` in `state_enter`, but we also disable it here in case the
// `os_inchar` branch was not executed (!multiqueue_empty(loop.events), which
// the `input_get` in `state_enter`, but we also disable it here in case the
// `input_get` branch was not executed (!multiqueue_empty(loop.events), which
// could have `may_garbage_collect` set to true in `normal_check`).
//
// That is because here we may run code that calls `os_inchar`
// That is because here we may run code that calls `input_get`
// later(`f_confirm` or `get_keystroke` for example), but in these cases it is
// not safe to perform garbage collection because there could be unreferenced
// lists or dicts being used.

View File

@ -210,39 +210,53 @@ static void set_init_default_backupskip(void)
OptIndex opt_idx = kOptBackupskip;
ga_init(&ga, 1, 100);
for (size_t n = 0; n < ARRAY_SIZE(names); n++) {
for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
bool mustfree = true;
char *p;
size_t plen;
#ifdef UNIX
if (*names[n] == NUL) {
if (*names[i] == NUL) {
# ifdef __APPLE__
p = "/private/tmp";
plen = STRLEN_LITERAL("/private/tmp");
# else
p = "/tmp";
plen = STRLEN_LITERAL("/tmp");
# endif
mustfree = false;
} else
#endif
{
p = vim_getenv(names[n]);
p = vim_getenv(names[i]);
plen = 0; // will be calcuated below
}
if (p != NULL && *p != NUL) {
// First time count the NUL, otherwise count the ','.
const size_t len = strlen(p) + 3;
char *item = xmalloc(len);
xstrlcpy(item, p, len);
add_pathsep(item);
xstrlcat(item, "*", len);
if (find_dup_item(ga.ga_data, item, options[opt_idx].flags)
== NULL) {
ga_grow(&ga, (int)len);
if (!GA_EMPTY(&ga)) {
strcat(ga.ga_data, ",");
bool has_trailing_path_sep = false;
if (plen == 0) {
// the value was retrieved from the environment
plen = strlen(p);
// does the value include a trailing path separator?
if (after_pathsep(p, p + plen)) {
has_trailing_path_sep = true;
}
strcat(ga.ga_data, p);
add_pathsep(ga.ga_data);
strcat(ga.ga_data, "*");
ga.ga_len += (int)len;
}
// item size needs to be large enough to include "/*" and a trailing NUL
// note: the value (and therefore plen) may already include a path separator
size_t itemsize = plen + (has_trailing_path_sep ? 0 : 1) + 2;
char *item = xmalloc(itemsize);
// add a preceeding comma as a separator after the first item
size_t itemseplen = (ga.ga_len == 0) ? 0 : 1;
size_t itemlen = (size_t)vim_snprintf(item, itemsize, "%s%s*", p,
has_trailing_path_sep ? "" : PATHSEPSTR);
if (find_dup_item(ga.ga_data, item, itemlen, options[opt_idx].flags) == NULL) {
ga_grow(&ga, (int)(itemseplen + itemlen + 1));
ga.ga_len += vim_snprintf((char *)ga.ga_data + ga.ga_len,
itemseplen + itemlen + 1,
"%s%s", (itemseplen > 0) ? "," : "", item);
}
xfree(item);
}
@ -526,7 +540,8 @@ static void set_string_default(OptIndex opt_idx, char *val, bool allocated)
/// For an option value that contains comma separated items, find "newval" in
/// "origval". Return NULL if not found.
static char *find_dup_item(char *origval, const char *newval, uint32_t flags)
static char *find_dup_item(char *origval, const char *newval, const size_t newvallen,
uint32_t flags)
FUNC_ATTR_NONNULL_ARG(2)
{
if (origval == NULL) {
@ -535,11 +550,10 @@ static char *find_dup_item(char *origval, const char *newval, uint32_t flags)
int bs = 0;
const size_t newlen = strlen(newval);
for (char *s = origval; *s != NUL; s++) {
if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1)))
&& strncmp(s, newval, newlen) == 0
&& (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) {
&& strncmp(s, newval, newvallen) == 0
&& (!(flags & P_COMMA) || s[newvallen] == ',' || s[newvallen] == NUL)) {
return s;
}
// Count backslashes. Only a comma with an even number of backslashes
@ -697,10 +711,10 @@ void set_helplang_default(const char *lang)
}
p_hlg = xmemdupz(lang, lang_len);
// zh_CN becomes "cn", zh_TW becomes "tw".
if (STRNICMP(p_hlg, "zh_", 3) == 0 && strlen(p_hlg) >= 5) {
if (STRNICMP(p_hlg, "zh_", 3) == 0 && lang_len >= 5) {
p_hlg[0] = (char)TOLOWER_ASC(p_hlg[3]);
p_hlg[1] = (char)TOLOWER_ASC(p_hlg[4]);
} else if (strlen(p_hlg) >= 1 && *p_hlg == 'C') {
} else if (lang_len && *p_hlg == 'C') {
// any C like setting, such as C.UTF-8, becomes "en"
p_hlg[0] = 'e';
p_hlg[1] = 'n';
@ -950,7 +964,7 @@ static char *stropt_get_newval(int nextchar, OptIndex opt_idx, char **argp, void
int len = 0;
if (op == OP_REMOVING || (flags & P_NODUP)) {
len = (int)strlen(newval);
s = find_dup_item(origval, newval, flags);
s = find_dup_item(origval, newval, (size_t)len, flags);
// do not add if already there
if ((op == OP_ADDING || op == OP_PREPENDING) && s != NULL) {
@ -1464,11 +1478,10 @@ int do_set(char *arg, int opt_flags)
}
if (errmsg != NULL) {
xstrlcpy(IObuff, _(errmsg), IOSIZE);
int i = (int)strlen(IObuff) + 2;
int i = vim_snprintf((char *)IObuff, IOSIZE, "%s", _(errmsg)) + 2;
if (i + (arg - startarg) < IOSIZE) {
// append the argument with the error
xstrlcat(IObuff, ": ", IOSIZE);
xstrlcpy(IObuff + i - 2, ": ", (size_t)(IOSIZE - i + 2));
assert(arg >= startarg);
memmove(IObuff + i, startarg, (size_t)(arg - startarg));
IObuff[i + (arg - startarg)] = NUL;
@ -5431,7 +5444,8 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
xp->xp_pattern = arg;
return;
}
char *p = arg + strlen(arg) - 1;
char *const argend = arg + strlen(arg);
char *p = argend - 1;
if (*p == ' ' && *(p - 1) != '\\') {
xp->xp_pattern = p + 1;
return;
@ -5618,7 +5632,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
// Triple-backslashed escaped file names (e.g. 'path') can also be
// delimited by space.
if ((flags & P_EXPAND) || (flags & P_COMMA) || (flags & P_COLON)) {
for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) {
for (p = argend - 1; p > xp->xp_pattern; p--) {
// count number of backslashes before ' ' or ','
if (*p == ' ' || *p == ',' || (*p == ':' && (flags & P_COLON))) {
char *s = p;
@ -5642,7 +5656,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
// An option that is a list of single-character flags should always start
// at the end as we don't complete words.
if (flags & P_FLAGLIST) {
xp->xp_pattern = arg + strlen(arg);
xp->xp_pattern = argend;
}
// Some options can either be using file/dir expansions, or custom value
@ -5952,7 +5966,7 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c
int count = 0;
(*matches)[count++] = xstrdup(option_val);
(*matches)[count++] = xmemdupz(option_val, num_flags);
if (num_flags > 1) {
// If more than one flags, split the flags up and expose each

View File

@ -9047,7 +9047,7 @@ return {
desc = [=[
When on, the title of the window will be set to the value of
'titlestring' (if it is not empty), or to:
filename [+=-] (path) - NVIM
filename [+=-] (path) - Nvim
Where:
filename the name of the file being edited
- indicates the file cannot be modified, 'ma' off
@ -9055,11 +9055,11 @@ return {
= indicates the file is read-only
=+ indicates the file is read-only and modified
(path) is the path of the file being edited
- NVIM the server name |v:servername| or "NVIM"
- Nvim the server name |v:servername| or "Nvim"
]=],
full_name = 'title',
scope = { 'global' },
short_desc = N_('Vim set the title of the window'),
short_desc = N_('set the title of the window'),
type = 'boolean',
varname = 'p_title',
},

View File

@ -33,12 +33,6 @@
#define READ_BUFFER_SIZE 0xfff
#define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN)
typedef enum {
kInputNone,
kInputAvail,
kInputEof,
} InbufPollResult;
static RStream read_stream = { .s.closed = true }; // Input before UI starts.
static char input_buffer[INPUT_BUFFER_SIZE];
static char *input_read_pos = input_buffer;
@ -84,57 +78,76 @@ static void cursorhold_event(void **argv)
static void create_cursorhold_event(bool events_enabled)
{
// If events are enabled and the queue has any items, this function should not
// have been called (inbuf_poll would return kInputAvail).
// have been called (`inbuf_poll` would return `kTrue`).
// TODO(tarruda): Cursorhold should be implemented as a timer set during the
// `state_check` callback for the states where it can be triggered.
assert(!events_enabled || multiqueue_empty(main_loop.events));
multiqueue_put(main_loop.events, cursorhold_event, NULL);
}
static void restart_cursorhold_wait(int tb_change_cnt)
static void reset_cursorhold_wait(int tb_change_cnt)
{
cursorhold_time = 0;
cursorhold_tb_change_cnt = tb_change_cnt;
}
/// Low level input function
/// Reads OS input into `buf`, and consumes pending events while waiting (if `ms != 0`).
///
/// Wait until either the input buffer is non-empty or, if `events` is not NULL
/// until `events` is non-empty.
int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events)
/// - Consumes available input received from the OS.
/// - Consumes pending events.
/// - Manages CursorHold events.
/// - Handles EOF conditions.
///
/// Originally based on the Vim `mch_inchar` function.
///
/// @param buf Buffer to store consumed input.
/// @param maxlen Maximum bytes to read into `buf`, or 0 to skip reading.
/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait.
/// @param tb_change_cnt Used to detect when typeahead changes.
/// @param events (optional) Events to process.
/// @return Bytes read into buf, or 0 if no input was read
int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events)
{
// This check is needed so that feeding typeahead from RPC can prevent CursorHold.
if (tb_change_cnt != cursorhold_tb_change_cnt) {
restart_cursorhold_wait(tb_change_cnt);
reset_cursorhold_wait(tb_change_cnt);
}
if (maxlen && input_available()) {
restart_cursorhold_wait(tb_change_cnt);
size_t to_read = MIN((size_t)maxlen, input_available());
memcpy(buf, input_read_pos, to_read);
input_read_pos += to_read;
return (int)to_read;
}
#define TRY_READ() \
do { \
if (maxlen && input_available()) { \
reset_cursorhold_wait(tb_change_cnt); \
assert(maxlen >= 0); \
size_t to_read = MIN((size_t)maxlen, input_available()); \
memcpy(buf, input_read_pos, to_read); \
input_read_pos += to_read; \
/* This is safe because INPUT_BUFFER_SIZE fits in an int. */ \
assert(to_read <= INT_MAX); \
return (int)to_read; \
} \
} while (0)
TRY_READ();
// No risk of a UI flood, so disable CTRL-C "interrupt" behavior if it's mapped.
if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) {
ctrl_c_interrupts = false;
}
InbufPollResult result;
TriState result; ///< inbuf_poll result.
if (ms >= 0) {
if ((result = inbuf_poll(ms, events)) == kInputNone) {
if ((result = inbuf_poll(ms, events)) == kFalse) {
return 0;
}
} else {
uint64_t wait_start = os_hrtime();
cursorhold_time = MIN(cursorhold_time, (int)p_ut);
if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) {
if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kFalse) {
if (read_stream.s.closed && silent_mode) {
// Drained eventloop & initial input; exit silent/batch-mode (-es/-Es).
read_error_exit();
}
restart_cursorhold_wait(tb_change_cnt);
reset_cursorhold_wait(tb_change_cnt);
if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) {
create_cursorhold_event(events == main_loop.events);
} else {
@ -153,32 +166,26 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
return 0;
}
if (maxlen && input_available()) {
restart_cursorhold_wait(tb_change_cnt);
// Safe to convert `to_read` to int, it will never overflow since
// INPUT_BUFFER_SIZE fits in an int
size_t to_read = MIN((size_t)maxlen, input_available());
memcpy(buf, input_read_pos, to_read);
input_read_pos += to_read;
return (int)to_read;
}
TRY_READ();
// If there are events, return the keys directly
if (maxlen && pending_events(events)) {
return push_event_key(buf, maxlen);
}
if (result == kInputEof) {
if (result == kNone) {
read_error_exit();
}
return 0;
#undef TRY_READ
}
// Check if a character is available for reading
bool os_char_avail(void)
{
return inbuf_poll(0, NULL) == kInputAvail;
return inbuf_poll(0, NULL) == kTrue;
}
/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed.
@ -463,15 +470,22 @@ bool input_blocking(void)
return blocking;
}
// This is a replacement for the old `WaitForChar` function in os_unix.c
static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
/// Checks for (but does not read) available input, and consumes `main_loop.events` while waiting.
///
/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait.
/// @param events (optional) Queue to check for pending events.
/// @return TriState:
/// - kTrue: Input/events available
/// - kFalse: No input/events
/// - kNone: EOF reached on the input stream
static TriState inbuf_poll(int ms, MultiQueue *events)
{
if (os_input_ready(events)) {
return kInputAvail;
return kTrue;
}
if (do_profiling == PROF_YES && ms) {
prof_inchar_enter();
prof_input_start();
}
if ((ms == -1 || ms > 0) && events != main_loop.events && !input_eof) {
@ -479,20 +493,18 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
blocking = true;
multiqueue_process_events(ch_before_blocking_events);
}
DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL,
events && !multiqueue_empty(events));
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms,
os_input_ready(events) || input_eof);
DLOG("blocking... events=%s", !!events ? "true" : "false");
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events) || input_eof);
blocking = false;
if (do_profiling == PROF_YES && ms) {
prof_inchar_exit();
prof_input_end();
}
if (os_input_ready(events)) {
return kInputAvail;
return kTrue;
}
return input_eof ? kInputEof : kInputNone;
return input_eof ? kNone : kFalse;
}
static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof)
@ -533,8 +545,8 @@ static void process_ctrl_c(void)
}
}
// Helper function used to push bytes from the 'event' key sequence partially
// between calls to os_inchar when maxlen < 3
/// Pushes bytes from the "event" key sequence (KE_EVENT) partially between calls to input_get when
/// `maxlen < 3`.
static int push_event_key(uint8_t *buf, int maxlen)
{
static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT };

View File

@ -1292,7 +1292,7 @@ static void shell_write_cb(Stream *stream, void *data, int status)
msg_schedule_semsg(_("E5677: Error writing input to shell-command: %s"),
uv_err_name(status));
}
stream_close(stream, NULL, NULL, false);
stream_may_close(stream, false);
}
/// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command.

View File

@ -383,19 +383,19 @@ void set_context_in_profile_cmd(expand_T *xp, const char *arg)
xp->xp_context = EXPAND_NOTHING;
}
static proftime_T inchar_time;
static proftime_T wait_time;
/// Called when starting to wait for the user to type a character.
void prof_inchar_enter(void)
void prof_input_start(void)
{
inchar_time = profile_start();
wait_time = profile_start();
}
/// Called when finished waiting for the user to type a character.
void prof_inchar_exit(void)
void prof_input_end(void)
{
inchar_time = profile_end(inchar_time);
profile_set_wait(profile_add(profile_get_wait(), inchar_time));
wait_time = profile_end(wait_time);
profile_set_wait(profile_add(profile_get_wait(), wait_time));
}
/// @return true when a function defined in the current script should be

View File

@ -74,10 +74,9 @@ getkey:
}
// Flush screen updates before blocking.
ui_flush();
// Call `os_inchar` directly to block for events or user input without
// consuming anything from `input_buffer`(os/input.c) or calling the
// mapping engine.
os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
// Call `input_get` directly to block for events or user input without consuming anything from
// `os/input.c:input_buffer` or calling the mapping engine.
input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
// If an event was put into the queue, we send K_EVENT directly.
if (!input_available() && !multiqueue_empty(main_loop.events)) {
key = K_EVENT;

View File

@ -22,6 +22,7 @@
#include "nvim/memory_defs.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/tui/tui.h"
#include "nvim/tui/tui_defs.h"
@ -126,6 +127,11 @@ void ui_client_run(bool remote_ui)
ui_client_attach(width, height, term, rgb);
// TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
if (os_env_exists("__NVIM_TEST_LOG")) {
ELOG("test log message");
}
// os_exit() will be invoked when the client channel detaches
while (true) {
LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1);

View File

@ -14,7 +14,7 @@ EXTERN size_t grid_line_buf_size INIT( = 0);
EXTERN schar_T *grid_line_buf_char INIT( = NULL);
EXTERN sattr_T *grid_line_buf_attr INIT( = NULL);
// ID of the ui client channel. If zero, the client is not running.
// Client-side UI channel. Zero during early startup or if not a (--remote-ui) UI client.
EXTERN uint64_t ui_client_channel_id INIT( = 0);
// exit status from embedded nvim process

View File

@ -3201,7 +3201,7 @@ describe('API', function()
end)
describe('nvim_get_runtime_file', function()
local p = n.alter_slashes
local p = t.fix_slashes
it('can find files', function()
eq({}, api.nvim_get_runtime_file('bork.borkbork', false))
eq({}, api.nvim_get_runtime_file('bork.borkbork', true))
@ -3210,36 +3210,36 @@ describe('API', function()
local val = api.nvim_get_runtime_file('autoload/remote/*.vim', true)
eq(2, #val)
if endswith(val[1], 'define.vim') then
ok(endswith(val[1], p 'autoload/remote/define.vim'))
ok(endswith(val[2], p 'autoload/remote/host.vim'))
ok(endswith(p(val[1]), 'autoload/remote/define.vim'))
ok(endswith(p(val[2]), 'autoload/remote/host.vim'))
else
ok(endswith(val[1], p 'autoload/remote/host.vim'))
ok(endswith(val[2], p 'autoload/remote/define.vim'))
ok(endswith(p(val[1]), 'autoload/remote/host.vim'))
ok(endswith(p(val[2]), 'autoload/remote/define.vim'))
end
val = api.nvim_get_runtime_file('autoload/remote/*.vim', false)
eq(1, #val)
ok(
endswith(val[1], p 'autoload/remote/define.vim')
or endswith(val[1], p 'autoload/remote/host.vim')
endswith(p(val[1]), 'autoload/remote/define.vim')
or endswith(p(val[1]), 'autoload/remote/host.vim')
)
val = api.nvim_get_runtime_file('lua', true)
eq(1, #val)
ok(endswith(val[1], p 'lua'))
ok(endswith(p(val[1]), 'lua'))
val = api.nvim_get_runtime_file('lua/vim', true)
eq(1, #val)
ok(endswith(val[1], p 'lua/vim'))
ok(endswith(p(val[1]), 'lua/vim'))
end)
it('can find directories', function()
local val = api.nvim_get_runtime_file('lua/', true)
eq(1, #val)
ok(endswith(val[1], p 'lua/'))
ok(endswith(p(val[1]), 'lua/'))
val = api.nvim_get_runtime_file('lua/vim/', true)
eq(1, #val)
ok(endswith(val[1], p 'lua/vim/'))
ok(endswith(p(val[1]), 'lua/vim/'))
eq({}, api.nvim_get_runtime_file('foobarlang/', true))
end)

View File

@ -9,7 +9,7 @@ local request = n.request
local is_os = t.is_os
describe('autocmd DirChanged and DirChangedPre', function()
local curdir = vim.uv.cwd():gsub('\\', '/')
local curdir = t.fix_slashes(vim.uv.cwd())
local dirs = {
curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
curdir .. '/Xtest-functional-autocmd-dirchanged.dir2',

View File

@ -1,5 +1,6 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local assert_log = t.assert_log
local clear = n.clear
@ -29,10 +30,54 @@ describe('log', function()
assert(request('nvim__stats').log_skip <= 13)
end)
it('messages are formatted with name or test id', function()
it('TUI client name is "ui"', function()
local function setup(env)
clear()
-- Start Nvim with builtin UI.
local screen = tt.setup_child_nvim({
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
n.nvim_set,
}, {
env = env,
})
screen:expect([[
{1: } |
~ |*4
|
{3:-- TERMINAL --} |
]])
end
-- Without $NVIM parent.
setup({
NVIM = '',
NVIM_LISTEN_ADDRESS = '',
NVIM_LOG_FILE = testlog,
__NVIM_TEST_LOG = '1',
})
-- Example:
-- ERR 2024-09-11T16:40:02.421 ui.47056 ui_client_run:165: test log message
assert_log(' ui%.%d+% +ui_client_run:%d+: test log message', testlog, 100)
-- With $NVIM parent.
setup({
NVIM_LOG_FILE = testlog,
__NVIM_TEST_LOG = '1',
})
-- Example:
-- ERR 2024-09-11T16:41:17.539 ui/c/T2.47826.0 ui_client_run:165: test log message
local tid = _G._nvim_test_id
assert_log(' ui/c/' .. tid .. '%.%d+%.%d +ui_client_run:%d+: test log message', testlog, 100)
end)
it('formats messages with session name or test id', function()
-- Examples:
-- ERR 2022-05-29T12:30:03.800 T2 log_init:110: test log message
-- ERR 2022-05-29T12:30:03.814 T2/child log_init:110: test log message
-- ERR 2024-09-11T16:44:33.794 T3.49429.0 server_init:58: test log message
-- ERR 2024-09-11T16:44:33.823 c/T3.49429.0 server_init:58: test log message
clear({
env = {
@ -47,10 +92,10 @@ describe('log', function()
exec_lua([[
local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1', '+foochild', '+qa!' }, vim.empty_dict())
vim.fn.jobwait({ j1 }, 10000)
vim.fn.jobwait({ j1 }, 5000)
]])
-- Child Nvim spawned by jobstart() appends "/c" to parent name.
assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100)
-- Child Nvim spawned by jobstart() prepends "c/" to parent name.
assert_log('c/' .. tid .. '%.%d+%.%d +server_init:%d+: test log message', testlog, 100)
end)
end)

View File

@ -27,7 +27,6 @@ local sleep = vim.uv.sleep
local startswith = vim.startswith
local write_file = t.write_file
local api = n.api
local alter_slashes = n.alter_slashes
local is_os = t.is_os
local dedent = t.dedent
local tbl_map = vim.tbl_map
@ -40,22 +39,15 @@ local testlog = 'Xtest-startupspec-log'
describe('startup', function()
it('--clean', function()
clear()
ok(
string.find(
alter_slashes(api.nvim_get_option_value('runtimepath', {})),
fn.stdpath('config'),
1,
true
) ~= nil
matches(
vim.pesc(t.fix_slashes(fn.stdpath('config'))),
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
clear('--clean')
ok(
string.find(
alter_slashes(api.nvim_get_option_value('runtimepath', {})),
fn.stdpath('config'),
1,
true
) == nil
not t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
:match(vim.pesc(t.fix_slashes(fn.stdpath('config'))))
)
end)

View File

@ -170,7 +170,7 @@ describe(':mksession', function()
skip(is_os('win'), 'causes rmdir() to fail')
local cwd_dir = fn.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
cwd_dir = cwd_dir:gsub([[\]], '/') -- :mksession always uses unix slashes.
cwd_dir = t.fix_slashes(cwd_dir) -- :mksession always uses unix slashes.
local session_path = cwd_dir .. '/' .. session_file
command('cd ' .. tab_dir)

View File

@ -217,7 +217,7 @@ describe('URI methods', function()
]],
file
)
local expected_uri = 'file:///' .. file:gsub('\\', '/')
local expected_uri = 'file:///' .. t.fix_slashes(file)
eq(expected_uri, exec_lua(test_case))
os.remove(file)
end)

View File

@ -22,7 +22,7 @@ describe("'autochdir'", function()
end)
it('is not overwritten by getwinvar() call #17609', function()
local curdir = vim.uv.cwd():gsub('\\', '/')
local curdir = t.fix_slashes(vim.uv.cwd())
local dir_a = curdir .. '/Xtest-functional-options-autochdir.dir_a'
local dir_b = curdir .. '/Xtest-functional-options-autochdir.dir_b'
mkdir(dir_a)

View File

@ -23,7 +23,6 @@ local insert = n.insert
local neq = t.neq
local mkdir = t.mkdir
local rmdir = n.rmdir
local alter_slashes = n.alter_slashes
local tbl_contains = vim.tbl_contains
local expect_exit = n.expect_exit
local check_close = n.check_close
@ -262,7 +261,7 @@ describe('startup defaults', function()
NVIM_LOG_FILE = '', -- Empty is invalid.
},
})
eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
eq(xdgstatedir .. '/log', t.fix_slashes(eval('$NVIM_LOG_FILE')))
end)
it('defaults to stdpath("log")/log if invalid', function()
@ -273,7 +272,7 @@ describe('startup defaults', function()
NVIM_LOG_FILE = '.', -- Any directory is invalid.
},
})
eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
eq(xdgstatedir .. '/log', t.fix_slashes(eval('$NVIM_LOG_FILE')))
-- Avoid "failed to open $NVIM_LOG_FILE" noise in test output.
expect_exit(command, 'qall!')
end)
@ -383,69 +382,69 @@ describe('XDG defaults', function()
eq(
(
(
t.fix_slashes(
root_path
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
):gsub('\\', '/')
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
)
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
command('set runtimepath&')
command('set backupdir&')
@ -454,85 +453,85 @@ describe('XDG defaults', function()
command('set viewdir&')
eq(
(
(
t.fix_slashes(
root_path
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
):gsub('\\', '/')
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
)
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
eq(
'.,' .. root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/backup//',
(api.nvim_get_option_value('backupdir', {}):gsub('\\', '/'))
t.fix_slashes(api.nvim_get_option_value('backupdir', {}))
)
eq(
root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/swap//',
(api.nvim_get_option_value('directory', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('directory', {}))
)
eq(
root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/undo//',
(api.nvim_get_option_value('undodir', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('undodir', {}))
)
eq(
root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/view//',
(api.nvim_get_option_value('viewdir', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('viewdir', {}))
)
end)
end)
@ -574,26 +573,26 @@ describe('XDG defaults', function()
local vimruntime, libdir = vimruntime_and_libdir()
eq(
(
(
t.fix_slashes(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/')
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
)
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
command('set runtimepath&')
command('set backupdir&')
@ -602,8 +601,47 @@ describe('XDG defaults', function()
command('set viewdir&')
eq(
(
(
t.fix_slashes(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
)
),
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
eq(
('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
t.fix_slashes(api.nvim_get_option_value('backupdir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
t.fix_slashes(api.nvim_get_option_value('directory', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
t.fix_slashes(api.nvim_get_option_value('undodir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
t.fix_slashes(api.nvim_get_option_value('viewdir', {}))
)
command('set all&')
eq(
t.fix_slashes(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
@ -619,63 +657,24 @@ describe('XDG defaults', function()
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/')
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
eq(
('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
api.nvim_get_option_value('backupdir', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('backupdir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
api.nvim_get_option_value('directory', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('directory', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
api.nvim_get_option_value('undodir', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('undodir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
api.nvim_get_option_value('viewdir', {}):gsub('\\', '/')
)
command('set all&')
eq(
(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/'),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
)
eq(
('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
api.nvim_get_option_value('backupdir', {}):gsub('\\', '/')
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
api.nvim_get_option_value('directory', {}):gsub('\\', '/')
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
api.nvim_get_option_value('undodir', {}):gsub('\\', '/')
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
api.nvim_get_option_value('viewdir', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('viewdir', {}))
)
eq(nil, (fn.tempname()):match('XDG_RUNTIME_DIR'))
end)
@ -939,7 +938,7 @@ describe('stdpath()', function()
local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '--listen', 'x', '+qall!' }, { env = { NVIM_APPNAME = %q } })
return vim.fn.jobwait({ child }, %d)[1]
]],
alter_slashes(testAppname),
testAppname,
3000
)
eq(expected_exitcode, exec_lua(lua_code))
@ -970,7 +969,7 @@ describe('stdpath()', function()
},
})
t.matches(vim.pesc(tmpdir), fn.tempname():gsub('\\', '/'))
t.matches(vim.pesc(tmpdir), t.fix_slashes(fn.tempname()))
t.assert_nolog('tempdir', testlog, 100)
t.assert_nolog('TMPDIR', testlog, 100)
t.matches([=[[/\\]relative%-appname.[^/\\]+]=], api.nvim_get_vvar('servername'))
@ -981,19 +980,19 @@ describe('stdpath()', function()
it('knows XDG_CONFIG_HOME', function()
clear({
env = {
XDG_CONFIG_HOME = alter_slashes('/home/docwhat/.config'),
XDG_CONFIG_HOME = '/home/docwhat/.config',
},
})
eq(alter_slashes('/home/docwhat/.config/nvim'), fn.stdpath('config'))
eq('/home/docwhat/.config/nvim', t.fix_slashes(fn.stdpath('config')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_CONFIG_HOME = alter_slashes('/home/original'),
XDG_CONFIG_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/nvim'), fn.stdpath('config'))
command("let $XDG_CONFIG_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/nvim'), fn.stdpath('config'))
eq('/home/original/nvim', t.fix_slashes(fn.stdpath('config')))
command("let $XDG_CONFIG_HOME='/home/new'")
eq('/home/new/nvim', t.fix_slashes(fn.stdpath('config')))
end)
it("doesn't expand $VARIABLES", function()
@ -1003,32 +1002,32 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/nvim'), fn.stdpath('config'))
eq('$VARIABLES/nvim', t.fix_slashes(fn.stdpath('config')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_CONFIG_HOME = alter_slashes('~/frobnitz'),
XDG_CONFIG_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/nvim'), fn.stdpath('config'))
eq('~/frobnitz/nvim', t.fix_slashes(fn.stdpath('config')))
end)
end)
describe('with "data"', function()
it('knows XDG_DATA_HOME', function()
clear({ env = {
XDG_DATA_HOME = alter_slashes('/home/docwhat/.local'),
XDG_DATA_HOME = '/home/docwhat/.local',
} })
eq(alter_slashes('/home/docwhat/.local/' .. datadir), fn.stdpath('data'))
eq('/home/docwhat/.local/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_DATA_HOME = alter_slashes('/home/original'),
XDG_DATA_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/' .. datadir), fn.stdpath('data'))
command("let $XDG_DATA_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/' .. datadir), fn.stdpath('data'))
eq('/home/original/' .. datadir, t.fix_slashes(fn.stdpath('data')))
command("let $XDG_DATA_HOME='/home/new'")
eq('/home/new/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
it("doesn't expand $VARIABLES", function()
@ -1038,14 +1037,14 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/' .. datadir), fn.stdpath('data'))
eq('$VARIABLES/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_DATA_HOME = alter_slashes('~/frobnitz'),
XDG_DATA_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/' .. datadir), fn.stdpath('data'))
eq('~/frobnitz/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
end)
@ -1053,19 +1052,19 @@ describe('stdpath()', function()
it('knows XDG_STATE_HOME', function()
clear({
env = {
XDG_STATE_HOME = alter_slashes('/home/docwhat/.local'),
XDG_STATE_HOME = '/home/docwhat/.local',
},
})
eq(alter_slashes('/home/docwhat/.local/' .. statedir), fn.stdpath('state'))
eq('/home/docwhat/.local/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_STATE_HOME = alter_slashes('/home/original'),
XDG_STATE_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/' .. statedir), fn.stdpath('state'))
command("let $XDG_STATE_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/' .. statedir), fn.stdpath('state'))
eq('/home/original/' .. statedir, t.fix_slashes(fn.stdpath('state')))
command("let $XDG_STATE_HOME='" .. '/home/new' .. "'")
eq('/home/new/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
it("doesn't expand $VARIABLES", function()
@ -1075,14 +1074,14 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/' .. statedir), fn.stdpath('state'))
eq('$VARIABLES/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_STATE_HOME = alter_slashes('~/frobnitz'),
XDG_STATE_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/' .. statedir), fn.stdpath('state'))
eq('~/frobnitz/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
end)
@ -1090,19 +1089,19 @@ describe('stdpath()', function()
it('knows XDG_CACHE_HOME', function()
clear({
env = {
XDG_CACHE_HOME = alter_slashes('/home/docwhat/.cache'),
XDG_CACHE_HOME = '/home/docwhat/.cache',
},
})
eq(alter_slashes('/home/docwhat/.cache/nvim'), fn.stdpath('cache'))
eq('/home/docwhat/.cache/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_CACHE_HOME = alter_slashes('/home/original'),
XDG_CACHE_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/nvim'), fn.stdpath('cache'))
command("let $XDG_CACHE_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/nvim'), fn.stdpath('cache'))
eq('/home/original/nvim', t.fix_slashes(fn.stdpath('cache')))
command("let $XDG_CACHE_HOME='" .. '/home/new' .. "'")
eq('/home/new/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
it("doesn't expand $VARIABLES", function()
@ -1112,14 +1111,14 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/nvim'), fn.stdpath('cache'))
eq('$VARIABLES/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_CACHE_HOME = alter_slashes('~/frobnitz'),
XDG_CACHE_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/nvim'), fn.stdpath('cache'))
eq('~/frobnitz/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
end)
end)
@ -1133,6 +1132,7 @@ describe('stdpath()', function()
HOMEDRIVE = 'C:',
HOMEPATH = '\\Users\\docwhat',
LOCALAPPDATA = 'C:\\Users\\docwhat\\AppData\\Local',
NVIM_LOG_FILE = testlog,
TEMP = 'C:\\Users\\docwhat\\AppData\\Local\\Temp',
TMPDIR = 'C:\\Users\\docwhat\\AppData\\Local\\Temp',
TMP = 'C:\\Users\\docwhat\\AppData\\Local\\Temp',
@ -1143,6 +1143,7 @@ describe('stdpath()', function()
HOMEDRIVE = 'HOMEDRIVE-should-be-ignored',
HOMEPATH = 'HOMEPATH-should-be-ignored',
LOCALAPPDATA = 'LOCALAPPDATA-should-be-ignored',
NVIM_LOG_FILE = testlog,
TEMP = 'TEMP-should-be-ignored',
TMPDIR = 'TMPDIR-should-be-ignored',
TMP = 'TMP-should-be-ignored',
@ -1166,12 +1167,18 @@ describe('stdpath()', function()
describe(msg, function()
it('set via system', function()
set_paths_via_system(env_var_name, paths)
eq(expected_paths, fn.stdpath(stdpath_arg))
eq(expected_paths, t.fix_slashes(fn.stdpath(stdpath_arg)))
if not is_os('win') then
assert_log('$TMPDIR tempdir not a directory.*TMPDIR%-should%-be%-ignored', testlog, 100)
end
end)
it('set at runtime', function()
set_paths_at_runtime(env_var_name, paths)
eq(expected_paths, fn.stdpath(stdpath_arg))
eq(expected_paths, t.fix_slashes(fn.stdpath(stdpath_arg)))
if not is_os('win') then
assert_log('$TMPDIR tempdir not a directory.*TMPDIR%-should%-be%-ignored', testlog, 100)
end
end)
end)
end
@ -1182,10 +1189,10 @@ describe('stdpath()', function()
'config_dirs',
'XDG_CONFIG_DIRS',
{
alter_slashes('/home/docwhat/.config'),
t.fix_slashes('/home/docwhat/.config'),
},
{
alter_slashes('/home/docwhat/.config/nvim'),
t.fix_slashes('/home/docwhat/.config/nvim'),
}
)
@ -1194,12 +1201,12 @@ describe('stdpath()', function()
'config_dirs',
'XDG_CONFIG_DIRS',
{
alter_slashes('/home/docwhat/.config'),
alter_slashes('/etc/config'),
t.fix_slashes('/home/docwhat/.config'),
t.fix_slashes('/etc/config'),
},
{
alter_slashes('/home/docwhat/.config/nvim'),
alter_slashes('/etc/config/nvim'),
t.fix_slashes('/home/docwhat/.config/nvim'),
t.fix_slashes('/etc/config/nvim'),
}
)
@ -1209,25 +1216,25 @@ describe('stdpath()', function()
'XDG_CONFIG_DIRS',
{ '$HOME', '$TMP' },
{
alter_slashes('$HOME/nvim'),
alter_slashes('$TMP/nvim'),
t.fix_slashes('$HOME/nvim'),
t.fix_slashes('$TMP/nvim'),
}
)
behaves_like_dir_list_env("doesn't expand ~/", 'config_dirs', 'XDG_CONFIG_DIRS', {
alter_slashes('~/.oldconfig'),
alter_slashes('~/.olderconfig'),
t.fix_slashes('~/.oldconfig'),
t.fix_slashes('~/.olderconfig'),
}, {
alter_slashes('~/.oldconfig/nvim'),
alter_slashes('~/.olderconfig/nvim'),
t.fix_slashes('~/.oldconfig/nvim'),
t.fix_slashes('~/.olderconfig/nvim'),
})
end)
describe('with "data_dirs"', function()
behaves_like_dir_list_env('knows XDG_DATA_DIRS with one path', 'data_dirs', 'XDG_DATA_DIRS', {
alter_slashes('/home/docwhat/.data'),
t.fix_slashes('/home/docwhat/.data'),
}, {
alter_slashes('/home/docwhat/.data/nvim'),
t.fix_slashes('/home/docwhat/.data/nvim'),
})
behaves_like_dir_list_env(
@ -1235,12 +1242,12 @@ describe('stdpath()', function()
'data_dirs',
'XDG_DATA_DIRS',
{
alter_slashes('/home/docwhat/.data'),
alter_slashes('/etc/local'),
t.fix_slashes('/home/docwhat/.data'),
t.fix_slashes('/etc/local'),
},
{
alter_slashes('/home/docwhat/.data/nvim'),
alter_slashes('/etc/local/nvim'),
t.fix_slashes('/home/docwhat/.data/nvim'),
t.fix_slashes('/etc/local/nvim'),
}
)
@ -1250,17 +1257,17 @@ describe('stdpath()', function()
'XDG_DATA_DIRS',
{ '$HOME', '$TMP' },
{
alter_slashes('$HOME/nvim'),
alter_slashes('$TMP/nvim'),
t.fix_slashes('$HOME/nvim'),
t.fix_slashes('$TMP/nvim'),
}
)
behaves_like_dir_list_env("doesn't expand ~/", 'data_dirs', 'XDG_DATA_DIRS', {
alter_slashes('~/.oldconfig'),
alter_slashes('~/.olderconfig'),
t.fix_slashes('~/.oldconfig'),
t.fix_slashes('~/.olderconfig'),
}, {
alter_slashes('~/.oldconfig/nvim'),
alter_slashes('~/.olderconfig/nvim'),
t.fix_slashes('~/.oldconfig/nvim'),
t.fix_slashes('~/.olderconfig/nvim'),
})
end)
end)

View File

@ -40,11 +40,22 @@ describe(':checkhealth', function()
matches('ERROR $VIM .* zub', curbuf_contents())
end)
it('completions can be listed via getcompletion()', function()
clear()
it('getcompletion()', function()
clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } }
eq('vim.deprecated', getcompletion('vim', 'checkhealth')[1])
eq('vim.provider', getcompletion('vim.prov', 'checkhealth')[1])
eq('vim.lsp', getcompletion('vim.ls', 'checkhealth')[1])
-- "test_plug/health/init.lua" should complete as "test_plug", not "test_plug.health". #30342
eq({
'test_plug',
'test_plug.full_render',
'test_plug.submodule',
'test_plug.submodule_empty',
'test_plug.success1',
'test_plug.success2',
}, getcompletion('test_plug', 'checkhealth'))
end)
it('completion checks for vim.health._complete() return type #28456', function()
@ -57,11 +68,9 @@ describe(':checkhealth', function()
end)
end)
describe('health.vim', function()
describe('vim.health', function()
before_each(function()
clear { args = { '-u', 'NORC' } }
-- Provides healthcheck functions
command('set runtimepath+=test/functional/fixtures')
clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } }
end)
describe(':checkhealth', function()
@ -207,9 +216,7 @@ end)
describe(':checkhealth window', function()
before_each(function()
clear { args = { '-u', 'NORC' } }
-- Provides healthcheck functions
command('set runtimepath+=test/functional/fixtures')
clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } }
command('set nofoldenable nowrap laststatus=0')
end)

View File

@ -342,7 +342,7 @@ describe(':terminal buffer', function()
command('wincmd p')
-- cwd will be inserted in a file URI, which cannot contain backs
local cwd = fn.getcwd():gsub('\\', '/')
local cwd = t.fix_slashes(fn.getcwd())
local parent = cwd:match('^(.+/)')
local expected = '\027]7;file://host' .. parent
api.nvim_chan_send(term, string.format('%s\027\\', expected))

View File

@ -902,26 +902,6 @@ function M.missing_provider(provider)
assert(false, 'Unknown provider: ' .. provider)
end
--- @param obj string|table
--- @return any
function M.alter_slashes(obj)
if not is_os('win') then
return obj
end
if type(obj) == 'string' then
local ret = obj:gsub('/', '\\')
return ret
elseif type(obj) == 'table' then
--- @cast obj table<any,any>
local ret = {} --- @type table<any,any>
for k, v in pairs(obj) do
ret[k] = M.alter_slashes(v)
end
return ret
end
assert(false, 'expected string or table of strings, got ' .. type(obj))
end
local load_factor = 1
if t.is_ci() then
-- Compute load factor only once (but outside of any tests).

View File

@ -8,6 +8,7 @@ local exec_lua = n.exec_lua
local pcall_err = t.pcall_err
local matches = t.matches
local insert = n.insert
local NIL = vim.NIL
before_each(clear)
@ -15,10 +16,12 @@ describe('treesitter language API', function()
-- error tests not requiring a parser library
it('handles missing language', function()
eq(
".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
'.../treesitter.lua:0: Parser not found.',
pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')")
)
eq(NIL, exec_lua("return vim.treesitter._get_parser(0, 'borklang')"))
-- actual message depends on platform
matches(
"Failed to load parser for language 'borklang': uv_dlopen: .+",
@ -105,9 +108,10 @@ describe('treesitter language API', function()
command('set filetype=borklang')
-- Should throw an error when filetype changes to borklang
eq(
".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
'.../treesitter.lua:0: Parser not found.',
pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0, 'borklang')")
)
eq(NIL, exec_lua("return vim.treesitter._get_parser(0, 'borklang')"))
end
)

View File

@ -135,9 +135,7 @@ void ui_refresh(void)
insert(test_text)
eq(
'.../treesitter.lua:0: There is no parser available for buffer 1 and one'
.. ' could not be created because lang could not be determined. Either'
.. ' pass lang or set the buffer filetype',
'.../treesitter.lua:0: Parser not found.',
pcall_err(exec_lua, 'vim.treesitter.get_parser(0)')
)

View File

@ -1344,6 +1344,53 @@ describe('float window', function()
|
]])
end
--
-- floating windows inherit NormalFloat from global-ns.
--
command('fclose')
command('hi NormalFloat guibg=LightRed')
api.nvim_open_win(0, false, { relative = 'win', row = 3, col = 3, width = 12, height = 3, style = 'minimal' })
api.nvim_set_hl_ns(api.nvim_create_namespace('test1'))
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
{14: 1 }^x |
{14: 2 }y |
{14: 3 } |
{0:~ }|*3
## grid 3
|
## grid 5
{22:x }|
{22:y }|
{22: }|
]], float_pos={
[5] = {1002, "NW", 2, 3, 3, true, 50};
}, win_viewport={
[2] = {win = 1000, topline = 0, botline = 4, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
[5] = {win = 1002, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}, win_viewport_margins={
[2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 },
[5] = { bottom = 0, left = 0, right = 0, top = 0, win = 1002 }
}})
else
screen:expect({
grid = [[
{14: 1 }^x |
{14: 2 }y |
{14: 3 } |
{0:~ }{22:x }{0: }|
{0:~ }{22:y }{0: }|
{0:~ }{22: }{0: }|
|
]]
})
end
end)
it("can use 'minimal' style", function()

View File

@ -22,7 +22,7 @@ describe('title', function()
end)
it('has correct default title with unnamed file', function()
local expected = '[No Name] - NVIM'
local expected = '[No Name] - Nvim'
command('set title')
screen:expect(function()
eq(expected, screen.title)
@ -30,7 +30,7 @@ describe('title', function()
end)
it('has correct default title with named file', function()
local expected = (is_os('win') and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM')
local expected = (is_os('win') and 'myfile (C:\\mydir) - Nvim' or 'myfile (/mydir) - Nvim')
command('set title')
command(is_os('win') and 'file C:\\mydir\\myfile' or 'file /mydir/myfile')
screen:expect(function()
@ -41,7 +41,7 @@ describe('title', function()
describe('is not changed by', function()
local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1'
local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2'
local expected = (is_os('win') and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM')
local expected = (is_os('win') and 'myfile1 (C:\\mydir) - Nvim' or 'myfile1 (/mydir) - Nvim')
local buf2
before_each(function()

View File

@ -7,11 +7,10 @@ local fnamemodify = n.fn.fnamemodify
local getcwd = n.fn.getcwd
local command = n.command
local write_file = t.write_file
local alter_slashes = n.alter_slashes
local is_os = t.is_os
local function eq_slashconvert(expected, got)
eq(alter_slashes(expected), alter_slashes(got))
eq(t.fix_slashes(expected), t.fix_slashes(got))
end
describe('fnamemodify()', function()

View File

@ -710,7 +710,7 @@ func s:GetFilenameChecks() abort
\ 'svg': ['file.svg'],
\ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'],
\ 'swayconfig': ['/home/user/.sway/config', '/home/user/.config/sway/config', '/etc/sway/config', '/etc/xdg/sway/config'],
\ 'swift': ['file.swift'],
\ 'swift': ['file.swift', 'file.swiftinterface'],
\ 'swiftgyb': ['file.swift.gyb'],
\ 'swig': ['file.swg', 'file.swig'],
\ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf', 'any/etc/sysctl.conf', 'any/etc/sysctl.d/file.conf'],

View File

@ -42,6 +42,29 @@ function M.isdir(path)
return stat.type == 'directory'
end
--- (Only on Windows) Replaces yucky "\\" slashes with delicious "/" slashes in a string, or all
--- string values in a table (recursively).
---
--- @param obj string|table
--- @return any
function M.fix_slashes(obj)
if not M.is_os('win') then
return obj
end
if type(obj) == 'string' then
local ret = obj:gsub('\\', '/')
return ret
elseif type(obj) == 'table' then
--- @cast obj table<any,any>
local ret = {} --- @type table<any,any>
for k, v in pairs(obj) do
ret[k] = M.fix_slashes(v)
end
return ret
end
assert(false, 'expected string or table of strings, got ' .. type(obj))
end
--- @param ... string|string[]
--- @return string
function M.argss_to_cmd(...)

975
test/unit/termkey_spec.lua Normal file
View File

@ -0,0 +1,975 @@
local t = require('test.unit.testutil')
local itp = t.gen_itp(it)
local bit = require('bit')
--- @alias TermKeyKey {utf8: string, type: integer, modifiers: integer, code: {codepoint: integer, sym: any, number: integer}}
--- @class termkey
--- @field TERMKEY_CANON_SPACESYMBOL integer
--- @field TERMKEY_FLAG_SPACESYMBOL integer
--- @field TERMKEY_FLAG_UTF8 integer
--- @field TERMKEY_FORMAT_ALTISMETA integer
--- @field TERMKEY_FORMAT_CARETCTRL integer
--- @field TERMKEY_FORMAT_LONGMOD integer
--- @field TERMKEY_FORMAT_LOWERMOD integer
--- @field TERMKEY_FORMAT_LOWERSPACE integer
--- @field TERMKEY_FORMAT_MOUSE_POS integer
--- @field TERMKEY_FORMAT_SPACEMOD integer
--- @field TERMKEY_FORMAT_WRAPBRACKET integer
--- @field TERMKEY_KEYMOD_ALT integer
--- @field TERMKEY_KEYMOD_CTRL integer
--- @field TERMKEY_MOUSE_DRAG integer
--- @field TERMKEY_MOUSE_PRESS integer
--- @field TERMKEY_MOUSE_RELEASE integer
--- @field TERMKEY_RES_AGAIN integer
--- @field TERMKEY_RES_KEY integer
--- @field TERMKEY_RES_NONE integer
--- @field TERMKEY_SYM_DOWN integer
--- @field TERMKEY_SYM_PAGEUP integer
--- @field TERMKEY_SYM_SPACE integer
--- @field TERMKEY_SYM_UNKNOWN integer
--- @field TERMKEY_SYM_UP integer
--- @field TERMKEY_TYPE_DCS integer
--- @field TERMKEY_TYPE_FUNCTION integer
--- @field TERMKEY_TYPE_KEYSYM integer
--- @field TERMKEY_TYPE_MODEREPORT integer
--- @field TERMKEY_TYPE_MOUSE integer
--- @field TERMKEY_TYPE_OSC integer
--- @field TERMKEY_TYPE_POSITION integer
--- @field TERMKEY_TYPE_UNICODE integer
--- @field TERMKEY_TYPE_UNKNOWN_CSI integer
--- @field termkey_canonicalise fun(any, any):any
--- @field termkey_destroy fun(any)
--- @field termkey_get_buffer_remaining fun(any):integer
--- @field termkey_get_buffer_size fun(any):integer
--- @field termkey_get_canonflags fun(any):any
--- @field termkey_get_keyname fun(any, any):any
--- @field termkey_getkey fun(any, any):any
--- @field termkey_getkey_force fun(any, any):any
--- @field termkey_interpret_csi fun(any, any, any, any, any):any
--- @field termkey_interpret_modereport fun(any, any, any, any, any):any
--- @field termkey_interpret_mouse fun(any, any, TermKeyKey, integer, integer, integer):any
--- @field termkey_interpret_position fun(any, any, any, any):any
--- @field termkey_interpret_string fun(any, TermKeyKey, any):any
--- @field termkey_lookup_keyname fun(any, any, any):any
--- @field termkey_new_abstract fun(string, integer):any
--- @field termkey_push_bytes fun(any, string, integer):integer
--- @field termkey_set_buffer_size fun(any, integer):integer
--- @field termkey_set_canonflags fun(any, any):any
--- @field termkey_set_flags fun(any, integer)
--- @field termkey_start fun(any):integer
--- @field termkey_stop fun(any):integer
--- @field termkey_strfkey fun(any, string, integer, any, any):integer
local termkey = t.cimport(
'./src/nvim/tui/termkey/termkey.h',
'./src/nvim/tui/termkey/termkey-internal.h',
'./src/nvim/tui/termkey/termkey_defs.h',
'./src/nvim/tui/termkey/driver-csi.h'
)
describe('termkey', function()
itp('01base', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
t.neq(tk, nil)
t.eq(termkey.termkey_get_buffer_size(tk), 256)
t.eq(tk.is_started, 1) -- tk->is_started true after construction
termkey.termkey_stop(tk)
t.neq(tk.is_started, 1) -- tk->is_started false after termkey_stop()
termkey.termkey_start(tk)
t.eq(tk.is_started, 1) -- tk->is_started true after termkey_start()
termkey.termkey_destroy(tk)
end)
itp('02getkey', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE when empty
t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1
t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after h
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after h
t.eq(key.code.codepoint, string.byte('h')) -- key.code.codepoint after h
t.eq(key.modifiers, 0) -- key.modifiers after h
t.eq(t.ffi.string(key.utf8), 'h') -- key.utf8 after h
t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free 256 after getkey
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE a second time
termkey.termkey_push_bytes(tk, '\x01', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after C-a
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after C-a
t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint after C-a
t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- key.modifiers after C-a
termkey.termkey_push_bytes(tk, '\033OA', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Up
-- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Up");
-- is_int(key.code.sym, TERMKEY_SYM_UP, "key.code.sym after Up");
t.eq(key.modifiers, 0) -- key.modifiers after Up
t.eq(termkey.termkey_push_bytes(tk, '\033O', 2), 2) -- push_bytes returns 2
-- is_int(termkey_get_buffer_remaining(tk), 254, "buffer free 254 after partial write");
-- is_int(termkey_getkey(tk, &key), TERMKEY_RES_AGAIN, "getkey yields RES_AGAIN after partial write");
termkey.termkey_push_bytes(tk, 'C', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Right completion
-- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Right");
-- is_int(key.code.sym, TERMKEY_SYM_RIGHT, "key.code.sym after Right");
-- is_int(key.modifiers, 0, "key.modifiers after Right");
-- is_int(termkey_get_buffer_remaining(tk), 256, "buffer free 256 after completion");
termkey.termkey_push_bytes(tk, '\033[27;5u', 7)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Escape
-- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Ctrl-Escape");
-- is_int(key.code.sym, TERMKEY_SYM_ESCAPE, "key.code.sym after Ctrl-Escape");
-- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Escape");
termkey.termkey_push_bytes(tk, '\0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Space
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after Ctrl-Space
-- t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after Ctrl-Space
-- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Space");
termkey.termkey_destroy(tk)
end)
itp('03utf8', function()
local tk = termkey.termkey_new_abstract('vt100', termkey.TERMKEY_FLAG_UTF8)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
termkey.termkey_push_bytes(tk, 'a', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY low ASCII
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type low ASCII
t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint low ASCII
-- 2-byte UTF-8 range is U+0080 to U+07FF (0xDF 0xBF)
-- However, we'd best avoid the C1 range, so we'll start at U+00A0 (0xC2 0xA0)
termkey.termkey_push_bytes(tk, '\xC2\xA0', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 low
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 low
t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 low
termkey.termkey_push_bytes(tk, '\xDF\xBF', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 high
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 high
t.eq(key.code.codepoint, 0x07FF) -- key.code.codepoint UTF-8 2 high
-- 3-byte UTF-8 range is U+0800 (0xE0 0xA0 0x80) to U+FFFD (0xEF 0xBF 0xBD)
termkey.termkey_push_bytes(tk, '\xE0\xA0\x80', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 low
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 low
t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 low
termkey.termkey_push_bytes(tk, '\xEF\xBF\xBD', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 high
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 high
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 high
-- 4-byte UTF-8 range is U+10000 (0xF0 0x90 0x80 0x80) to U+10FFFF (0xF4 0x8F 0xBF 0xBF)
termkey.termkey_push_bytes(tk, '\xF0\x90\x80\x80', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 low
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 low
t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 low
termkey.termkey_push_bytes(tk, '\xF4\x8F\xBF\xBF', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 high
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 high
t.eq(key.code.codepoint, 0x10FFFF) -- key.code.codepoint UTF-8 4 high
-- Invalid continuations
termkey.termkey_push_bytes(tk, '\xC2!', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid cont
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 2 invalid cont
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 2 invalid after
termkey.termkey_push_bytes(tk, '\xE0!', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after
termkey.termkey_push_bytes(tk, '\xE0\xA0!', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont 2
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont 2
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after
termkey.termkey_push_bytes(tk, '\xF0!', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after
termkey.termkey_push_bytes(tk, '\xF0\x90!', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 2
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 2
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after
termkey.termkey_push_bytes(tk, '\xF0\x90\x80!', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 3
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 3
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after
-- Partials
termkey.termkey_push_bytes(tk, '\xC2', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 2 partial
termkey.termkey_push_bytes(tk, '\xA0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 partial
t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 partial
termkey.termkey_push_bytes(tk, '\xE0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial
termkey.termkey_push_bytes(tk, '\xA0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial
termkey.termkey_push_bytes(tk, '\x80', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 partial
t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 partial
termkey.termkey_push_bytes(tk, '\xF0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial
termkey.termkey_push_bytes(tk, '\x90', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial
termkey.termkey_push_bytes(tk, '\x80', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial
termkey.termkey_push_bytes(tk, '\x80', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 partial
t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 partial
termkey.termkey_destroy(tk)
end)
itp('04flags', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
termkey.termkey_push_bytes(tk, ' ', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after space
t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after space
t.eq(key.modifiers, 0) -- key.modifiers after space
termkey.termkey_set_flags(tk, termkey.TERMKEY_FLAG_SPACESYMBOL)
termkey.termkey_push_bytes(tk, ' ', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space
t.eq(key.type, termkey.TERMKEY_TYPE_KEYSYM) -- key.type after space with FLAG_SPACESYMBOL
t.eq(key.code.sym, termkey.TERMKEY_SYM_SPACE) -- key.code.sym after space with FLAG_SPACESYMBOL
t.eq(key.modifiers, 0) -- key.modifiers after space with FLAG_SPACESYMBOL
termkey.termkey_destroy(tk)
end)
itp('06buffer', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256
t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size initially 256
t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1
t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes
t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size 256 after push_bytes
t.eq(not not termkey.termkey_set_buffer_size(tk, 512), true) -- buffer set size OK
t.eq(termkey.termkey_get_buffer_remaining(tk), 511) -- buffer free 511 after push_bytes
t.eq(termkey.termkey_get_buffer_size(tk), 512) -- buffer size 512 after push_bytes
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- buffered key still useable after resize
termkey.termkey_destroy(tk)
end)
local function termkey_keyname2sym(tk, keyname)
local sym = t.ffi.new('TermKeySym[1]')
local endp = termkey.termkey_lookup_keyname(tk, keyname, sym)
if endp == nil then
return termkey.TERMKEY_SYM_UNKNOWN
end
return sym
end
itp('10keyname', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local sym = termkey_keyname2sym(tk, 'SomeUnknownKey')
t.eq(sym, termkey.TERMKEY_SYM_UNKNOWN) -- keyname2sym SomeUnknownKey
sym = termkey_keyname2sym(tk, 'Space')
t.eq(sym[0], termkey.TERMKEY_SYM_SPACE) -- keyname2sym Space
local _end = termkey.termkey_lookup_keyname(tk, 'Up', sym)
t.neq(_end, nil) -- termkey_get_keyname Up returns non-NULL
t.eq(t.ffi.string(_end), '') -- termkey_get_keyname Up return points at endofstring
t.eq(sym[0], termkey.TERMKEY_SYM_UP) -- termkey_get_keyname Up yields Up symbol
_end = termkey.termkey_lookup_keyname(tk, 'DownMore', sym)
t.neq(_end, nil) -- termkey_get_keyname DownMore returns non-NULL
t.eq(t.ffi.string(_end), 'More') -- termkey_get_keyname DownMore return points at More
t.eq(sym[0], termkey.TERMKEY_SYM_DOWN) -- termkey_get_keyname DownMore yields Down symbol
_end = termkey.termkey_lookup_keyname(tk, 'SomeUnknownKey', sym)
t.eq(_end, nil) -- termkey_get_keyname SomeUnknownKey returns NULL
t.eq(t.ffi.string(termkey.termkey_get_keyname(tk, termkey.TERMKEY_SYM_SPACE)), 'Space') -- "get_keyname SPACE");
termkey.termkey_destroy(tk)
end)
itp('11strfkey', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
---@type TermKeyKey
local key = t.ffi.new(
'TermKeyKey',
{ type = termkey.TERMKEY_TYPE_UNICODE, code = { codepoint = string.byte('A') } }
)
local buffer = t.ffi.new('char[16]')
local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 1) -- length for unicode/A/0
t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 1) -- length for unicode/A/0 wrapbracket
t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0 wrapbracket
---@type TermKeyKey
key = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('b') },
modifiers = termkey.TERMKEY_KEYMOD_CTRL,
})
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 3) -- length for unicode/b/CTRL
t.eq(t.ffi.string(buffer), 'C-b') -- buffer for unicode/b/CTRL
len =
termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD)
t.eq(len, 6) -- length for unicode/b/CTRL longmod
t.eq(t.ffi.string(buffer), 'Ctrl-b') -- buffer for unicode/b/CTRL longmod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_SPACEMOD)
)
t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod
t.eq(t.ffi.string(buffer), 'Ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_LOWERMOD)
)
t.eq(len, 6) -- length for unicode/b/CTRL longmod|lowermod
t.eq(t.ffi.string(buffer), 'ctrl-b') -- buffer for unicode/b/CTRL longmod|lowermod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(
termkey.TERMKEY_FORMAT_LONGMOD,
termkey.TERMKEY_FORMAT_SPACEMOD,
termkey.TERMKEY_FORMAT_LOWERMOD
)
)
t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod|lowermode
t.eq(t.ffi.string(buffer), 'ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod|lowermode
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_CARETCTRL
)
t.eq(len, 2) -- length for unicode/b/CTRL caretctrl
t.eq(t.ffi.string(buffer), '^B') -- buffer for unicode/b/CTRL caretctrl
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 5) -- length for unicode/b/CTRL wrapbracket
t.eq(t.ffi.string(buffer), '<C-b>') -- buffer for unicode/b/CTRL wrapbracket
---@type TermKeyKey
key = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('c') },
modifiers = termkey.TERMKEY_KEYMOD_ALT,
})
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 3) -- length for unicode/c/ALT
t.eq(t.ffi.string(buffer), 'A-c') -- buffer for unicode/c/ALT
len =
termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD)
t.eq(len, 5) -- length for unicode/c/ALT longmod
t.eq(t.ffi.string(buffer), 'Alt-c') -- buffer for unicode/c/ALT longmod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_ALTISMETA
)
t.eq(len, 3) -- length for unicode/c/ALT altismeta
t.eq(t.ffi.string(buffer), 'M-c') -- buffer for unicode/c/ALT altismeta
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_ALTISMETA)
)
t.eq(len, 6) -- length for unicode/c/ALT longmod|altismeta
t.eq(t.ffi.string(buffer), 'Meta-c') -- buffer for unicode/c/ALT longmod|altismeta
---@type TermKeyKey
key = t.ffi.new(
'TermKeyKey',
{ type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_UP } }
)
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 2) -- length for sym/Up/0
t.eq(t.ffi.string(buffer), 'Up') -- buffer for sym/Up/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 4) -- length for sym/Up/0 wrapbracket
t.eq(t.ffi.string(buffer), '<Up>') -- buffer for sym/Up/0 wrapbracket
---@type TermKeyKey
key = t.ffi.new(
'TermKeyKey',
{ type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_PAGEUP } }
)
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 6) -- length for sym/PageUp/0
t.eq(t.ffi.string(buffer), 'PageUp') -- buffer for sym/PageUp/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_LOWERSPACE
)
t.eq(len, 7) -- length for sym/PageUp/0 lowerspace
t.eq(t.ffi.string(buffer), 'page up') -- buffer for sym/PageUp/0 lowerspace
-- If size of buffer is too small, strfkey should return something consistent
len = termkey.termkey_strfkey(tk, buffer, 4, key, 0)
t.eq(len, 6) -- length for sym/PageUp/0
t.eq(t.ffi.string(buffer), 'Pag') -- buffer of len 4 for sym/PageUp/0
len = termkey.termkey_strfkey(tk, buffer, 4, key, termkey.TERMKEY_FORMAT_LOWERSPACE)
t.eq(len, 7) -- length for sym/PageUp/0 lowerspace
t.eq(t.ffi.string(buffer), 'pag') -- buffer of len 4 for sym/PageUp/0 lowerspace
key = t.ffi.new('TermKeyKey', { type = termkey.TERMKEY_TYPE_FUNCTION, code = { number = 5 } }) ---@type TermKeyKey
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 2) -- length for func/5/0
t.eq(t.ffi.string(buffer), 'F5') -- buffer for func/5/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 4) -- length for func/5/0 wrapbracket
t.eq(t.ffi.string(buffer), '<F5>') -- buffer for func/5/0 wrapbracket
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_LOWERSPACE
)
t.eq(len, 2) -- length for func/5/0 lowerspace
t.eq(t.ffi.string(buffer), 'f5') -- buffer for func/5/0 lowerspace
termkey.termkey_destroy(tk)
end)
itp('13cmpkey', function()
local function termkey_keycmp(tk, key1, key2)
termkey.termkey_canonicalise(tk, key1)
termkey.termkey_canonicalise(tk, key2)
if key1.type ~= key2.type then
return key1.type - key2.type
end
if key1.type == termkey.TERMKEY_TYPE_UNICODE then
if key1.code.codepoint ~= key2.code.codepoint then
return key1.code.codepoint - key2.code.codepoint
end
end
return key1.modifiers - key2.modifiers
end
local tk = termkey.termkey_new_abstract('vt100', 0)
---@type TermKeyKey
local key1 = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('A') },
modifiers = 0,
})
---@type TermKeyKey
local key2 = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('A') },
modifiers = 0,
})
t.eq(termkey_keycmp(tk, key1, key1), 0) -- cmpkey same structure
t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey identical structure
key2.modifiers = termkey.TERMKEY_KEYMOD_CTRL
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders CTRL after nomod
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders nomod before CTRL
key2.code.codepoint = string.byte('B')
key2.modifiers = 0
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders 'B' after 'A'
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders 'A' before 'B'
key1.modifiers = termkey.TERMKEY_KEYMOD_CTRL
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders nomod 'B' after CTRL 'A'
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders CTRL 'A' before nomod 'B'
key2.type = termkey.TERMKEY_TYPE_KEYSYM
key2.code.sym = termkey.TERMKEY_SYM_UP
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders KEYSYM after UNICODE
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders UNICODE before KEYSYM
key1.type = termkey.TERMKEY_TYPE_KEYSYM
key1.code.sym = termkey.TERMKEY_SYM_SPACE
key1.modifiers = 0
key2.type = termkey.TERMKEY_TYPE_UNICODE
key2.code.codepoint = string.byte(' ')
key2.modifiers = 0
t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey considers KEYSYM/SPACE and UNICODE/SP identical
termkey.termkey_set_canonflags(
tk,
bit.bor(termkey.termkey_get_canonflags(tk), termkey.TERMKEY_CANON_SPACESYMBOL)
)
t.eq(termkey_keycmp(tk, key1, key2), 0) -- "cmpkey considers KEYSYM/SPACE and UNICODE/SP identical under SPACESYMBOL");
termkey.termkey_destroy(tk)
end)
itp('30mouse', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey', { type = -1 }) ---@type TermKeyKey
local ev = t.ffi.new('TermKeyMouseEvent[1]')
local button = t.ffi.new('int[1]')
local line = t.ffi.new('int[1]')
local col = t.ffi.new('int[1]')
local buffer = t.ffi.new('char[32]')
termkey.termkey_push_bytes(tk, '\x1b[M !!', 6)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press
t.eq(button[0], 1) -- mouse button for press
t.eq(line[0], 1) -- mouse line for press
t.eq(col[0], 1) -- mouse column for press
t.eq(key.modifiers, 0) -- modifiers for press
local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 13) -- string length for press
t.eq(t.ffi.string(buffer), 'MousePress(1)') -- string buffer for press
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_MOUSE_POS
)
t.eq(len, 21) -- string length for press
t.eq(t.ffi.string(buffer), 'MousePress(1) @ (1,1)') -- string buffer for press
termkey.termkey_push_bytes(tk, '\x1b[M@"!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_DRAG) -- mouse event for drag
t.eq(button[0], 1) -- mouse button for drag
t.eq(line[0], 1) -- mouse line for drag
t.eq(col[0], 2) -- mouse column for drag
t.eq(key.modifiers, 0) -- modifiers for press
termkey.termkey_push_bytes(tk, '\x1b[M##!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release
t.eq(line[0], 1) -- mouse line for release
t.eq(col[0], 3) -- mouse column for release
t.eq(key.modifiers, 0) -- modifiers for press
termkey.termkey_push_bytes(tk, '\x1b[M0++', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for Ctrl-press
t.eq(button[0], 1) -- mouse button for Ctrl-press
t.eq(line[0], 11) -- mouse line for Ctrl-press
t.eq(col[0], 11) -- mouse column for Ctrl-press
t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- modifiers for Ctrl-press
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 15) -- string length for Ctrl-press
t.eq(t.ffi.string(buffer), 'C-MousePress(1)') -- string buffer for Ctrl-press
termkey.termkey_push_bytes(tk, '\x1b[M`!!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel down
t.eq(button[0], 4) -- mouse button for wheel down
termkey.termkey_push_bytes(tk, '\x1b[Mb!!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel left
t.eq(button[0], 6) -- mouse button for wheel left
-- rxvt protocol
termkey.termkey_push_bytes(tk, '\x1b[0;20;20M', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press rxvt protocol
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press rxvt protocol
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press rxvt protocol
t.eq(button[0], 1) -- mouse button for press rxvt protocol
t.eq(line[0], 20) -- mouse line for press rxvt protocol
t.eq(col[0], 20) -- mouse column for press rxvt protocol
t.eq(key.modifiers, 0) -- modifiers for press rxvt protocol
termkey.termkey_push_bytes(tk, '\x1b[3;20;20M', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release rxvt protocol
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release rxvt protocol
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release rxvt protocol
t.eq(line[0], 20) -- mouse line for release rxvt protocol
t.eq(col[0], 20) -- mouse column for release rxvt protocol
t.eq(key.modifiers, 0) -- modifiers for release rxvt protocol
-- SGR protocol
termkey.termkey_push_bytes(tk, '\x1b[<0;30;30M', 11)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press SGR encoding
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press SGR encoding
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press SGR
t.eq(button[0], 1) -- mouse button for press SGR
t.eq(line[0], 30) -- mouse line for press SGR
t.eq(col[0], 30) -- mouse column for press SGR
t.eq(key.modifiers, 0) -- modifiers for press SGR
termkey.termkey_push_bytes(tk, '\x1b[<0;30;30m', 11)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release SGR encoding
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release SGR encoding
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release SGR
termkey.termkey_push_bytes(tk, '\x1b[<0;500;300M', 13)
termkey.termkey_getkey(tk, key)
termkey.termkey_interpret_mouse(tk, key, ev, button, line, col)
t.eq(line[0], 300) -- mouse line for press SGR wide
t.eq(col[0], 500) -- mouse column for press SGR wide
termkey.termkey_destroy(tk)
end)
itp('31position', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
local line = t.ffi.new('int[1]')
local col = t.ffi.new('int[1]')
termkey.termkey_push_bytes(tk, '\x1b[?15;7R', 8)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for position report
t.eq(key.type, termkey.TERMKEY_TYPE_POSITION) -- key.type for position report
t.eq(termkey.termkey_interpret_position(tk, key, line, col), termkey.TERMKEY_RES_KEY) -- interpret_position yields RES_KEY
t.eq(line[0], 15) -- line for position report
t.eq(col[0], 7) -- column for position report
-- A plain CSI R is likely to be <F3> though.
-- This is tricky :/
termkey.termkey_push_bytes(tk, '\x1b[R', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for <F3>
t.eq(key.type, termkey.TERMKEY_TYPE_FUNCTION) -- key.type for <F3>
t.eq(key.code.number, 3) -- key.code.number for <F3>
termkey.termkey_destroy(tk)
end)
itp('32modereport', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
local initial = t.ffi.new('int[1]')
local mode = t.ffi.new('int[1]')
local value = t.ffi.new('int[1]')
termkey.termkey_push_bytes(tk, '\x1b[?1;2$y', 8)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report
t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report
t.eq(
termkey.termkey_interpret_modereport(tk, key, initial, mode, value),
termkey.TERMKEY_RES_KEY
) -- interpret_modereoprt yields RES_KEY
t.eq(initial[0], 63) -- initial indicator from mode report
t.eq(mode[0], 1) -- mode number from mode report
t.eq(value[0], 2) -- mode value from mode report
termkey.termkey_push_bytes(tk, '\x1b[4;1$y', 7)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report
t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report
t.eq(
termkey.termkey_interpret_modereport(tk, key, initial, mode, value),
termkey.TERMKEY_RES_KEY
) -- interpret_modereoprt yields RES_KEY
t.eq(initial[0], 0) -- initial indicator from mode report
t.eq(mode[0], 4) -- mode number from mode report
t.eq(value[0], 1) -- mode value from mode report
termkey.termkey_destroy(tk)
end)
itp('38csi', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
local args = t.ffi.new('TermKeyCsiParam[16]')
local nargs = t.ffi.new('size_t[1]')
local command = t.ffi.new('unsigned[1]')
termkey.termkey_push_bytes(tk, '\x1b[5;25v', 7)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI v
t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI
t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY
t.eq(nargs[0], 2) -- nargs for unknown CSI
-- t.eq(args[0], 5) -- args[0] for unknown CSI
-- t.eq(args[1], 25) -- args[1] for unknown CSI
t.eq(command[0], 118) -- command for unknown CSI
termkey.termkey_push_bytes(tk, '\x1b[?w', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? w
t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI
t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY
t.eq(command[0], bit.bor(bit.lshift(63, 8), 119)) -- command for unknown CSI
termkey.termkey_push_bytes(tk, '\x1b[?$x', 5)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? $x
t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI
t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY
t.eq(command[0], bit.bor(bit.lshift(36, 16), bit.lshift(63, 8), 120)) -- command for unknown CSI
termkey.termkey_destroy(tk)
end)
itp('39dcs', function()
local tk = termkey.termkey_new_abstract('xterm', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
-- 7bit DCS
termkey.termkey_push_bytes(tk, '\x1bP1$r1 q\x1b\\', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS
t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS
t.eq(key.modifiers, 0) -- key.modifiers for DCS
local str = t.ffi.new('const char*[1]')
t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- termkey_interpret_string() gives string
t.eq(t.ffi.string(str[0]), '1$r1 q') -- termkey_interpret_string() yields correct string
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE
-- 8bit DCS
termkey.termkey_push_bytes(tk, '\x901$r2 q\x9c', 8)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS
t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS
t.eq(key.modifiers, 0) -- key.modifiers for DCS
t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string");
t.eq(t.ffi.string(str[0]), '1$r2 q') -- "termkey_interpret_string() yields correct string");
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- "getkey again yields RES_NONE");
-- 7bit OSC
termkey.termkey_push_bytes(tk, '\x1b]15;abc\x1b\\', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for OSC
t.eq(key.type, termkey.TERMKEY_TYPE_OSC) -- key.type for OSC
t.eq(key.modifiers, 0) -- key.modifiers for OSC
t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string");
t.eq(t.ffi.string(str[0]), '15;abc') -- "termkey_interpret_string() yields correct string");
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE
-- False alarm
termkey.termkey_push_bytes(tk, '\x1bP', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN for false alarm
t.eq(termkey.termkey_getkey_force(tk, key), termkey.TERMKEY_RES_KEY) -- getkey_force yields RES_KEY for false alarm
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type for false alarm
t.eq(key.code.codepoint, string.byte('P')) -- key.code.codepoint for false alarm
t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_ALT) -- key.modifiers for false alarm
termkey.termkey_destroy(tk)
end)
end)