Compare commits

...

51 Commits

Author SHA1 Message Date
Jonathon
8e44cc44d2
Merge 931b3f0693 into a0d8c2b86e 2024-09-16 07:10:34 -04:00
glepnir
a0d8c2b86e
docs(eval): update param types of prompt-buffer functions (#30392) 2024-09-16 18:33:35 +08:00
Justin M. Keyes
549c00c791
Merge #29490 feat(vim.ui.open): configurable opener 2024-09-16 03:21:40 -07:00
Justin M. Keyes
23dcd7cd73 test(vim.ui.open): opt.cmd 2024-09-16 11:58:04 +02:00
Matěj Cepl
3f15e57b26 feat(vim.ui): configurable "gx" / vim.ui.open() tool
Problem:
User cannot configure the tool used by `vim.ui.open` (or `gx`). With
netrw this was supported by `g:netrw_browsex_viewer`.

Solution:
Introduce `opts.cmd`. Users that want to set this globally can
monkey-patch `vim.ui.open` in the same way described at `:help vim.paste()`.

Fixes https://github.com/neovim/neovim/issues/29488

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2024-09-16 11:58:02 +02:00
Christian Clason
a9031cc4a6 vim-patch:5e95c8f: runtime(java): Highlight javaConceptKind modifiers with StorageClass
Stop assigning by default the NonText highlighting group for
javaConceptKind modifiers since its colour is hardly
distinguishable from a background colour for a range of
colour schemes.

fixes vim/vim#15237
related vim/vim#15238
closes: vim/vim#15664

5e95c8f637

Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com>
Co-authored-by: Dexter Gaon-Shatford <dexter@gaonshatford.ca>
2024-09-16 08:16:32 +02:00
Christian Clason
5e7933693b vim-patch:0f5effb: runtime(netrw): delete confirmation not strict enough
fixes: vim/vim#15680

0f5effbd1f

Co-authored-by: Christian Brabandt <cb@256bit.org>
2024-09-16 08:16:20 +02:00
Christian Clason
f408603f4f vim-patch:9.1.0731: inconsistent case sensitive extension matching
Problem:  inconsistent case sensitive extension matching
Solution: unify case sensitive extension matching (Evgeni Chasnovski).

There are different approaches of how extensions are matched with
respect to case sensitivity. In particular, '\c' flag is used in pattern
whereas in most places case sensitive matching is guarded behind
`has("fname_case")` condition.

Replace all instances of '\c' with an explicit case sensitive pattern
variants guarded by `has("fname_case")`. Strictly speaking, this is a
breaking change because only two (most common and prevailingly tested)
variants are now matched: upper first letter and upper all letters.

closes: vim/vim#15672

59b089c9df

Co-authored-by: Evgeni Chasnovski <evgeni.chasnovski@gmail.com>
2024-09-16 08:16:07 +02:00
zeertzjq
78b8510933
vim-patch:4d427d4: runtime(vim): Update base-syntax, match Vim9 bool/null literal args to :if/:while/:return (#30391)
Match Vim9 boolean and null literals in expression arguments of :if,
:elseif, :while and :return.

closes: vim/vim#15684

4d427d4cab

Co-authored-by: Doug Kearns <dougkearns@gmail.com>
2024-09-15 22:57:16 +00:00
Justin M. Keyes
057d27a9d6
refactor: rename "process" => "proc" #30387
Problem:
- "process" is often used as a verb (`multiqueue_process_events`), which
  is ambiguous for cases where it's used as a topic.
- The documented naming convention for processes is "proc".
  - `:help dev-name-common`
- Shorter is better, when it doesn't harm readability or
  discoverability.

Solution:
Rename "process" => "proc" in all C symbols and module names.
2024-09-15 12:20:58 -07:00
Justin M. Keyes
5792546777
refactor(tests): rename terminal/testutil.lua => testterm.lua #30372
This module is generally used by any tests that need the full Nvim TUI
instead of `screen.lua`. Thus it should live in `functional/` instead of
in `functional/terminal/`.
2024-09-15 03:28:14 -07:00
zeertzjq
3b54adc6c6
Merge pull request #30378 from zeertzjq/vim-9.1.0729
vim-patch:9.1.{0729,0730}
2024-09-14 19:51:11 +08:00
zeertzjq
325d349f9d
vim-patch:9.1.0728: [security]: heap-use-after-free in garbage collection with location list user data (#30377)
Problem:  heap-use-after-free in garbage collection with location list
          user data.
Solution: Mark user data as in use when no other window is referencing
          the location list (zeertzjq)

fixes: neovim/neovim#30371
closes: vim/vim#15683

be4bd189d2
2024-09-14 19:38:33 +08:00
zeertzjq
5191a11d66 vim-patch:9.1.0730: Crash with cursor-screenline and narrow window
Problem:  Crash with cursor-screenline and narrow window
          (elig0n)
Solution: Don't set right_col when width2 is 0 (zeertzjq).

fixes: vim/vim#15677
closes: vim/vim#15678

59149f0269
2024-09-14 19:29:40 +08:00
zeertzjq
90585e47fe vim-patch:9.1.0729: Wrong cursor-screenline when resizing window
Problem:  Wrong cursor-screenline when resizing window
Solution: Invalidate saved left_col and right_col when width1 or width2
          change.

closes: vim/vim#15679

86dc4f8b43
2024-09-14 19:29:37 +08:00
Jaehwang Jung
f2173b1aa2
fix(defaults): cannot remove "How-to disable mouse" menu item #30375 2024-09-14 02:18:38 -07:00
dundargoc
67d6b6f27e ci: skip automerge step if backport failed 2024-09-14 01:55:36 +02:00
Mathias Fussenegger
8512f669f0 fix(lsp): handle nil bytes in strings
Problem:

The LSP omnifunc can insert nil bytes, which when read in other places
(like semantic token) could cause an error:

    semantic_tokens.lua:304: Vim:E976: Using a Blob as a String

Solution:

Use `#line` instead of `vim.fn.strlen(line)`. Both return UTF-8 bytes
but the latter can't handle nil bytes.

Completion candidates can currently insert nil bytes, if other parts of
Alternative fix to https://github.com/neovim/neovim/pull/30359

Note that https://github.com/neovim/neovim/pull/30315 will avoid the
insertion of nil bytes from the LSP omnifunc, but the change of this PR
can more easily be backported.
2024-09-13 22:34:49 +02: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
jonathon
931b3f0693 fix: memory leak and out of bounds access fixes 2024-08-29 10:20:06 -04:00
Jonathon
e78ed6f347 remove redundant cast to int 2024-08-07 23:05:45 -04:00
Jonathon
66df7c3413 changes from make format 2024-08-07 23:05:04 -04:00
Jonathon
66715e9ce3 explicit cast to size_t 2024-08-03 08:29:07 -04:00
Jonathon
9ebcc1131b remove int to size t comparison 2024-08-03 08:29:07 -04:00
Jonathon
8985e9292b explicit type conversion 2024-08-03 08:29:07 -04:00
Jonathon
0e110d180f remove warning 2024-08-03 08:29:07 -04:00
Jonathon
7d07185d99 remove trailing white space from lua test 2024-08-03 08:29:06 -04:00
Jonathon
729fa8ea7d refractoring, and commenting 2024-08-03 08:29:06 -04:00
Jonathon
eae703b044 free iwhite_index_offset, and remove initialization because it's unecessary 2024-08-03 08:29:06 -04:00
Jonathon
867ee3a892 free decisions (fix memory leak) 2024-08-03 08:29:06 -04:00
Jonathon
65f6a6b76b include a test for linematch, include ignore white processing for all
algorithms in same place
2024-08-03 08:29:06 -04:00
Jonathon
209fc88c8f add functional tests for utf-8 characters 2024-08-03 08:29:06 -04:00
Jonathon
9247062387 fix type errors 2024-08-03 08:29:06 -04:00
Jonathon
d9a3363896 handle utf-8 characters as tokens consisting of multiple characters 2024-08-03 08:29:06 -04:00
Jonathon
0371a006d0 fix linter errors 2024-08-03 08:29:06 -04:00
Jonathon
34cf5a3fee test fix 2024-08-03 08:29:06 -04:00
Jonathon
f40e576ebc build fix 2024-08-03 08:29:06 -04:00
Jonathon
e76b0f2990 remove false from ml_get_buf 2024-08-03 08:29:06 -04:00
Jonathon
d21977db34 move the -1 to outside function call 2024-08-03 08:29:06 -04:00
Jonathon
b3119cba2b tests with ignore white 2024-08-03 08:29:06 -04:00
Jonathon
027ee9ebed remove beforeeach 2024-08-03 08:29:06 -04:00
Jonathon
17e1d68529 3 tests working for word match 2024-08-03 08:29:06 -04:00
Jonathon
95c2958324 add test for ignore whitespace 2024-08-03 08:29:06 -04:00
Jonathon
bcd5311279 test for iwhite 2024-08-03 08:29:06 -04:00
Jonathon
7e63013bd3 add unit tests for charmatch 2024-08-03 08:29:06 -04:00
Jonathon
0db7e59325 charmatch / wordmatch - cross line character-wise diff highlighting
and comparison grouping optimization
2024-08-03 08:28:47 -04:00
85 changed files with 1660 additions and 598 deletions

View File

@ -26,3 +26,21 @@ 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
if: ${{ steps.backport.outputs.was_successful == 'true' }}
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

@ -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/ab28a2ddb86a144194a798bcf1fef1ab4f981ab7.tar.gz
UNIBILIUM_SHA256 7c0386fc48452632868e0dcb8e7ed140d67a3da79e39ceee54c1ba7a495ad625
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

View File

@ -23,6 +23,7 @@
" 2024 Aug 15 by Vim Project: style changes, prevent E121 (#15501)
" 2024 Aug 22 by Vim Project: fix mf-selection highlight (#15551)
" 2024 Aug 22 by Vim Project: adjust echo output of mx command (#15550)
" 2024 Sep 15 by Vim Project: more strict confirmation dialog (#15680)
" }}}
" Former Maintainer: Charles E Campbell
" GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
@ -11275,7 +11276,7 @@ fun! s:NetrwLocalRm(path) range
let ok= s:NetrwLocalRmFile(a:path,fname,all)
if ok =~# 'q\%[uit]' || ok == "no"
break
elseif ok =~# 'a\%[ll]'
elseif ok =~# '^a\%[ll]$'
let all= 1
endif
endfor
@ -11304,7 +11305,7 @@ fun! s:NetrwLocalRm(path) range
let ok= s:NetrwLocalRmFile(a:path,curword,all)
if ok =~# 'q\%[uit]' || ok == "no"
break
elseif ok =~# 'a\%[ll]'
elseif ok =~# '^a\%[ll]$'
let all= 1
endif
let ctr= ctr + 1
@ -11351,12 +11352,12 @@ fun! s:NetrwLocalRmFile(path,fname,all)
" call Decho("response: ok<".ok.">",'~'.expand("<slnum>"))
let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
" call Decho("response: ok<".ok."> (after sub)",'~'.expand("<slnum>"))
if ok =~# 'a\%[ll]'
if ok =~# '^a\%[ll]$'
let all= 1
endif
endif
if all || ok =~# 'y\%[es]' || ok == ""
if all || ok =~# '^y\%[es]$' || ok == ""
let ret= s:NetrwDelete(rmfile)
" call Decho("errcode=".v:shell_error." ret=".ret,'~'.expand("<slnum>"))
endif
@ -11372,13 +11373,13 @@ fun! s:NetrwLocalRmFile(path,fname,all)
if ok == ""
let ok="no"
endif
if ok =~# 'a\%[ll]'
if ok =~# '^a\%[ll]$'
let all= 1
endif
endif
let rmfile= substitute(rmfile,'[\/]$','','e')
if all || ok =~# 'y\%[es]' || ok == ""
if all || ok =~# '^y\%[es]$' || ok == ""
if delete(rmfile,"rf")
call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".rmfile.">!",103)
endif

View File

@ -2559,7 +2559,7 @@ vim.ui.input({opts}, {on_confirm}) *vim.ui.input()*
typed (it might be an empty string if nothing was
entered), or `nil` if the user aborted the dialog.
vim.ui.open({path}) *vim.ui.open()*
vim.ui.open({path}, {opt}) *vim.ui.open()*
Opens `path` with the system default handler (macOS `open`, Windows
`explorer.exe`, Linux `xdg-open`, …), or returns (but does not show) an
error message on failure.
@ -2570,6 +2570,8 @@ vim.ui.open({path}) *vim.ui.open()*
-- Asynchronous.
vim.ui.open("https://neovim.io/")
vim.ui.open("~/path/to/file")
-- Use the "osurl" command to handle the path or URL.
vim.ui.open("gh#neovim/neovim!29490", { cmd = { 'osurl' } })
-- Synchronous (wait until the process exits).
local cmd, err = vim.ui.open("$VIMRUNTIME")
if cmd then
@ -2579,6 +2581,8 @@ vim.ui.open({path}) *vim.ui.open()*
Parameters: ~
• {path} (`string`) Path or URL to open
• {opt} (`{ cmd?: string[] }?`) Options
• cmd string[]|nil Command used to open the path or URL.
Return (multiple): ~
(`vim.SystemObj?`) Command object, or nil if not found.

View File

@ -194,7 +194,10 @@ TUI
UI
• TODO
• |vim.ui.open()| (by default bound to |gx|) accepts an `opt.cmd` parameter
which controls the tool used to open the given path or URL. If you want to
globally set this, you can override vim.ui.open using the same approach
described at |vim.paste()|.
==============================================================================
CHANGED FEATURES *news-changed*

View File

@ -1683,6 +1683,12 @@ In order to highlight nested parens with different colors, define colors for
or >
:hi javaParen ctermfg=blue guifg=#0000ff
Certain modifiers are incompatible with each other, e.g. `abstract` and
`final`: >
:syn list javaConceptKind
and can be differently highlighted as a group than other modifiers with >
:hi link javaConceptKind NonText
If you notice highlighting errors while scrolling backwards, which are fixed
when redrawing with CTRL-L, try setting the "g:java_minlines" variable to
a larger number: >

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

@ -213,7 +213,6 @@ end
--- Default menus
do
--- Right click popup menu
local function def_menu(ctx)
vim.cmd([[
anoremenu PopUp.Go\ to\ definition <Cmd>lua vim.lsp.buf.definition()<CR>
amenu PopUp.Open\ in\ web\ browser gx
@ -229,7 +228,10 @@ do
inoremenu PopUp.Select\ All <C-Home><C-O>VG
anoremenu PopUp.-2- <Nop>
anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR>
]])
local function enable_ctx_menu(ctx)
vim.cmd([[
amenu disable PopUp.Go\ to\ definition
amenu disable PopUp.Open\ in\ web\ browser
]])
@ -240,7 +242,6 @@ do
vim.cmd([[anoremenu enable PopUp.Go\ to\ definition]])
end
end
def_menu()
local nvim_popupmenu_augroup = vim.api.nvim_create_augroup('nvim_popupmenu', {})
vim.api.nvim_create_autocmd('MenuPopup', {
@ -252,7 +253,7 @@ do
local urls = require('vim.ui')._get_urls()
local url = vim.startswith(urls[1], 'http')
local ctx = url and 'url' or (vim.lsp.get_clients({ bufnr = 0 })[1] and 'lsp' or nil)
def_menu(ctx)
enable_ctx_menu(ctx)
end,
})
end

View File

@ -6703,7 +6703,7 @@ function vim.fn.printf(fmt, expr1) end
--- If the buffer doesn't exist or isn't a prompt buffer, an empty
--- string is returned.
---
--- @param buf any
--- @param buf integer|string
--- @return any
function vim.fn.prompt_getprompt(buf) end
@ -6738,8 +6738,8 @@ function vim.fn.prompt_getprompt(buf) end
--- endfunc
--- call prompt_setcallback(bufnr(), function('s:TextEntered'))
---
--- @param buf any
--- @param expr any
--- @param buf integer|string
--- @param expr string|function
--- @return any
function vim.fn.prompt_setcallback(buf, expr) end
@ -6751,8 +6751,8 @@ function vim.fn.prompt_setcallback(buf, expr) end
--- mode. Without setting a callback Vim will exit Insert mode,
--- as in any buffer.
---
--- @param buf any
--- @param expr any
--- @param buf integer|string
--- @param expr string|function
--- @return any
function vim.fn.prompt_setinterrupt(buf, expr) end
@ -6763,8 +6763,8 @@ function vim.fn.prompt_setinterrupt(buf, expr) end
--- call prompt_setprompt(bufnr(''), 'command: ')
--- <
---
--- @param buf any
--- @param text any
--- @param buf integer|string
--- @param text string
--- @return any
function vim.fn.prompt_setprompt(buf, text) end

View File

@ -280,12 +280,7 @@ local extension = {
cfi = 'cf',
hgrc = 'cfg',
cfg = detect.cfg,
cfG = detect.cfg,
cFg = detect.cfg,
cFG = detect.cfg,
Cfg = detect.cfg,
CfG = detect.cfg,
CFg = detect.cfg,
CFG = detect.cfg,
chf = 'ch',
chai = 'chaiscript',
@ -370,12 +365,7 @@ local extension = {
drt = 'dart',
ds = 'datascript',
dat = detect.dat,
daT = detect.dat,
dAt = detect.dat,
dAT = detect.dat,
Dat = detect.dat,
DaT = detect.dat,
DAt = detect.dat,
DAT = detect.dat,
dcd = 'dcd',
decl = detect.decl,
@ -658,12 +648,7 @@ local extension = {
kt = 'kotlin',
ktm = 'kotlin',
sub = 'krl',
suB = 'krl',
sUb = 'krl',
sUB = 'krl',
Sub = 'krl',
SuB = 'krl',
SUb = 'krl',
SUB = 'krl',
ks = 'kscript',
k = 'kwt',
@ -699,12 +684,7 @@ local extension = {
lite = 'lite',
livemd = 'livebook',
log = detect.log,
loG = detect.log,
lOg = detect.log,
lOG = detect.log,
Log = detect.log,
LoG = detect.log,
LOg = detect.log,
LOG = detect.log,
lgt = 'logtalk',
lotos = 'lotos',
@ -773,12 +753,7 @@ local extension = {
mmp = 'mmp',
mms = detect.mms,
mod = detect.mod,
moD = detect.mod,
mOd = detect.mod,
mOD = detect.mod,
Mod = detect.mod,
MoD = detect.mod,
MOd = detect.mod,
MOD = detect.mod,
DEF = 'modula2',
m3 = 'modula3',
@ -948,12 +923,7 @@ local extension = {
ih = 'ppwiz',
action = 'privoxy',
prg = detect.prg,
prG = detect.prg,
pRg = detect.prg,
pRG = detect.prg,
Prg = detect.prg,
PrG = detect.prg,
PRg = detect.prg,
PRG = detect.prg,
pc = 'proc',
pdb = 'prolog',
@ -1133,12 +1103,7 @@ local extension = {
sqr = 'sqr',
nut = 'squirrel',
src = detect.src,
srC = detect.src,
sRc = detect.src,
sRC = detect.src,
Src = detect.src,
SrC = detect.src,
SRc = detect.src,
SRC = detect.src,
s28 = 'srec',
s37 = 'srec',
@ -1168,12 +1133,7 @@ local extension = {
swig = 'swig',
swg = 'swig',
sys = detect.sys,
syS = detect.sys,
sYs = detect.sys,
sYS = detect.sys,
Sys = detect.sys,
SyS = detect.sys,
SYs = detect.sys,
SYS = detect.sys,
svh = 'systemverilog',
sv = 'systemverilog',

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

@ -154,7 +154,7 @@ end
---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16
---@return integer byte (utf-8) index of `encoding` index `index` in `line`
function M._str_byteindex_enc(line, index, encoding)
local len = vim.fn.strlen(line)
local len = #line
if index > len then
-- LSP spec: if character > line length, default to the line length.
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position
@ -167,7 +167,7 @@ function M._str_byteindex_enc(line, index, encoding)
if index then
return index
else
return #line
return len
end
elseif encoding == 'utf-16' then
return vim.str_byteindex(line, index, true)

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

@ -117,6 +117,8 @@ end
--- -- Asynchronous.
--- vim.ui.open("https://neovim.io/")
--- vim.ui.open("~/path/to/file")
--- -- Use the "osurl" command to handle the path or URL.
--- vim.ui.open("gh#neovim/neovim!29490", { cmd = { 'osurl' } })
--- -- Synchronous (wait until the process exits).
--- local cmd, err = vim.ui.open("$VIMRUNTIME")
--- if cmd then
@ -125,12 +127,14 @@ end
--- ```
---
---@param path string Path or URL to open
---@param opt? { cmd?: string[] } Options
--- - cmd string[]|nil Command used to open the path or URL.
---
---@return vim.SystemObj|nil # Command object, or nil if not found.
---@return nil|string # Error message on failure, or nil on success.
---
---@see |vim.system()|
function M.open(path)
function M.open(path, opt)
vim.validate({
path = { path, 'string' },
})
@ -139,12 +143,13 @@ function M.open(path)
path = vim.fs.normalize(path)
end
local cmd --- @type string[]
local opts --- @type vim.SystemOpts
opt = opt or {}
local cmd ---@type string[]
local job_opt = { text = true, detach = true } --- @type vim.SystemOpts
opts = { text = true, detach = true }
if vim.fn.has('mac') == 1 then
if opt.cmd then
cmd = vim.list_extend(opt.cmd --[[@as string[] ]], { path })
elseif vim.fn.has('mac') == 1 then
cmd = { 'open', path }
elseif vim.fn.has('win32') == 1 then
if vim.fn.executable('rundll32') == 1 then
@ -154,8 +159,8 @@ function M.open(path)
end
elseif vim.fn.executable('xdg-open') == 1 then
cmd = { 'xdg-open', path }
opts.stdout = false
opts.stderr = false
job_opt.stdout = false
job_opt.stderr = false
elseif vim.fn.executable('wslview') == 1 then
cmd = { 'wslview', path }
elseif vim.fn.executable('explorer.exe') == 1 then
@ -164,7 +169,7 @@ function M.open(path)
return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)'
end
return vim.system(cmd, opts), nil
return vim.system(cmd, job_opt), nil
end
--- Returns all URLs at cursor, if any.

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

@ -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 Sep 10
" Last Change: 2024 Sep 11
" Please check :help java.vim for comments on some of the options available.
@ -663,7 +663,7 @@ hi def link javaStorageClass StorageClass
hi def link javaMethodDecl javaStorageClass
hi def link javaClassDecl javaStorageClass
hi def link javaScopeDecl javaStorageClass
hi def link javaConceptKind NonText
hi def link javaConceptKind javaStorageClass
hi def link javaBoolean Boolean
hi def link javaSpecial Special

View File

@ -685,7 +685,7 @@ if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_novimfunctionerror")
syn match vimBufnrWarn /\<bufnr\s*(\s*["']\.['"]\s*)/
endif
syn match vimNotFunc "\<if\>\|\<el\%[seif]\>\|\<retu\%[rn]\>\|\<while\>" skipwhite nextgroup=vimOper,vimOperParen,vimVar,vimFunc,vimNotation
syn match vimNotFunc "\<if\>\|\<el\%[seif]\>\|\<retu\%[rn]\>\|\<while\>" skipwhite nextgroup=@vimExprList,vimNotation
" Match: {{{2
" =====

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

@ -10,7 +10,7 @@
Memcheck:Leak
fun:malloc
fun:uv_spawn
fun:libuv_process_spawn
fun:process_spawn
fun:libuv_proc_spawn
fun:proc_spawn
fun:job_start
}

View File

@ -848,7 +848,7 @@ def CheckIncludes(filename, lines, error):
or filename.endswith('.in.h')
or FileInfo(filename).RelativePath() in {
'func_attr.h',
'os/pty_process.h',
'os/pty_proc.h',
}):
return
@ -869,7 +869,7 @@ def CheckIncludes(filename, lines, error):
"src/nvim/msgpack_rpc/unpacker.h",
"src/nvim/option.h",
"src/nvim/os/pty_conpty_win.h",
"src/nvim/os/pty_process_win.h",
"src/nvim/os/pty_proc_win.h",
]
skip_headers = [

View File

@ -417,10 +417,10 @@ list(SORT NVIM_HEADERS)
foreach(sfile ${NVIM_SOURCES})
get_filename_component(f ${sfile} NAME)
if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
if(WIN32 AND ${f} MATCHES "^(pty_proc_unix.c)$")
list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$")
if(NOT WIN32 AND ${f} MATCHES "^(pty_proc_win.c)$")
list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
if(NOT WIN32 AND ${f} MATCHES "^(pty_conpty_win.c)$")
@ -436,7 +436,7 @@ foreach(hfile ${NVIM_HEADERS})
if(WIN32 AND ${f} MATCHES "^(unix_defs.h)$")
list(REMOVE_ITEM NVIM_HEADERS ${hfile})
endif()
if(WIN32 AND ${f} MATCHES "^(pty_process_unix.h)$")
if(WIN32 AND ${f} MATCHES "^(pty_proc_unix.h)$")
list(REMOVE_ITEM NVIM_HEADERS ${hfile})
endif()
if(NOT WIN32 AND ${f} MATCHES "^(win_defs.h)$")
@ -832,12 +832,12 @@ find_program(CLANG_TIDY_PRG clang-tidy)
set(EXCLUDE_CLANG_TIDY typval_encode.c.h ui_events.in.h)
if(WIN32)
list(APPEND EXCLUDE_CLANG_TIDY
os/pty_process_unix.h
os/pty_proc_unix.h
os/unix_defs.h)
else()
list(APPEND EXCLUDE_CLANG_TIDY
os/win_defs.h
os/pty_process_win.h
os/pty_proc_win.h
os/pty_conpty_win.h
os/os_win_console.h)
endif()

View File

@ -70,7 +70,7 @@
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/process.h"
#include "nvim/os/proc.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/runtime.h"

View File

@ -763,6 +763,8 @@ struct diffblock_S {
linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines
bool is_linematched; // has the linematch algorithm ran on this diff hunk to divide it into
// smaller diff hunks?
size_t n_charmatch;
int *charmatchp; // values for charmatch
};
#define SNAP_HELP_IDX 0

View File

@ -19,7 +19,7 @@
#include "nvim/eval/typval.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/socket.h"
#include "nvim/event/stream.h"
@ -88,7 +88,7 @@ void channel_free_all_mem(void)
bool channel_close(uint64_t id, ChannelPart part, const char **error)
{
Channel *chan;
Process *proc;
Proc *proc;
const char *dummy;
if (!error) {
@ -139,8 +139,8 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
if (part == kChannelPartStderr || part == kChannelPartAll) {
rstream_may_close(&proc->err);
}
if (proc->type == kProcessTypePty && part == kChannelPartAll) {
pty_process_close_master(&chan->stream.pty);
if (proc->type == kProcTypePty && part == kChannelPartAll) {
pty_proc_close_master(&chan->stream.pty);
}
break;
@ -289,7 +289,7 @@ static void channel_destroy(Channel *chan)
}
if (chan->streamtype == kChannelStreamProc) {
process_free(&chan->stream.proc);
proc_free(&chan->stream.proc);
}
callback_reader_free(&chan->on_data);
@ -376,7 +376,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
*status_out = 0;
return NULL;
}
chan->stream.pty = pty_process_init(&main_loop, chan);
chan->stream.pty = pty_proc_init(&main_loop, chan);
if (pty_width > 0) {
chan->stream.pty.width = pty_width;
}
@ -384,22 +384,22 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
chan->stream.pty.height = pty_height;
}
} else {
chan->stream.uv = libuv_process_init(&main_loop, chan);
chan->stream.uv = libuv_proc_init(&main_loop, chan);
}
Process *proc = &chan->stream.proc;
Proc *proc = &chan->stream.proc;
proc->argv = argv;
proc->exepath = exepath;
proc->cb = channel_process_exit_cb;
proc->cb = channel_proc_exit_cb;
proc->events = chan->events;
proc->detach = detach;
proc->cwd = cwd;
proc->env = env;
proc->overlapped = overlapped;
char *cmd = xstrdup(process_get_exepath(proc));
char *cmd = xstrdup(proc_get_exepath(proc));
bool has_out, has_err;
if (proc->type == kProcessTypePty) {
if (proc->type == kProcTypePty) {
has_out = true;
has_err = false;
} else {
@ -410,7 +410,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
bool has_in = stdin_mode == kChannelStdinPipe;
int status = process_spawn(proc, has_in, has_out, has_err);
int status = proc_spawn(proc, has_in, has_out, has_err);
if (status) {
semsg(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
@ -760,7 +760,7 @@ void channel_reader_callbacks(Channel *chan, CallbackReader *reader)
}
}
static void channel_process_exit_cb(Process *proc, int status, void *data)
static void channel_proc_exit_cb(Proc *proc, int status, void *data)
{
Channel *chan = data;
if (chan->term) {
@ -847,7 +847,7 @@ static void term_write(const char *buf, size_t size, void *data)
static void term_resize(uint16_t width, uint16_t height, void *data)
{
Channel *chan = data;
pty_process_resize(&chan->stream.pty, width, height);
pty_proc_resize(&chan->stream.pty, width, height);
}
static inline void term_delayed_free(void **argv)
@ -867,7 +867,7 @@ static inline void term_delayed_free(void **argv)
static void term_close(void *data)
{
Channel *chan = data;
process_stop(&chan->stream.proc);
proc_stop(&chan->stream.proc);
multiqueue_put(chan->events, term_delayed_free, data);
}
@ -907,7 +907,7 @@ bool channel_job_running(uint64_t id)
Channel *chan = find_channel(id);
return (chan
&& chan->streamtype == kChannelStreamProc
&& !process_is_stopped(&chan->stream.proc));
&& !proc_is_stopped(&chan->stream.proc));
}
Dictionary channel_info(uint64_t id, Arena *arena)
@ -924,8 +924,8 @@ Dictionary channel_info(uint64_t id, Arena *arena)
switch (chan->streamtype) {
case kChannelStreamProc: {
stream_desc = "job";
if (chan->stream.proc.type == kProcessTypePty) {
const char *name = pty_process_tty_name(&chan->stream.pty);
if (chan->stream.proc.type == kProcTypePty) {
const char *name = pty_proc_tty_name(&chan->stream.pty);
PUT_C(info, "pty", CSTR_TO_ARENA_OBJ(arena, name));
}

View File

@ -7,11 +7,11 @@
#include "nvim/channel_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/libuv_proc.h"
#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/pty_proc.h"
#include "nvim/types_defs.h"
struct Channel {
@ -21,9 +21,9 @@ struct Channel {
ChannelStreamType streamtype;
union {
Process proc;
LibuvProcess uv;
PtyProcess pty;
Proc proc;
LibuvProc uv;
PtyProc pty;
RStream socket;
StdioPair stdio;
StderrState err;

View File

@ -10,6 +10,7 @@
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@ -84,11 +85,15 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option
#define DIFF_LINEMATCH 0x1000 // match most similar lines within diff
#define DIFF_CHARDIFF 0x2000 // character-wise matching
#define DIFF_WORDDIFF 0x4000 // character-wise matching
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static int diff_algorithm = 0;
static int linematch_lines = 0;
static int chardiff_chars = 0;
static int worddiff_words = 0;
#define LBUFLEN 50 // length of line in diff file
@ -130,6 +135,11 @@ typedef enum {
DIFF_NONE,
} diffstyle_T;
typedef enum {
LINEMATCH,
CHARMATCH,
WORDMATCH,
} diff_allignment_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.c.generated.h"
#endif
@ -522,6 +532,7 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
{
diff_T *dnew = xmalloc(sizeof(*dnew));
dnew->charmatchp = NULL;
dnew->is_linematched = false;
dnew->df_next = dp;
if (dprev == NULL) {
@ -536,6 +547,7 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
static diff_T *diff_free(tabpage_T *tp, diff_T *dprev, diff_T *dp)
{
diff_T *ret = dp->df_next;
xfree(dp->charmatchp);
xfree(dp);
if (dprev == NULL) {
@ -1647,6 +1659,7 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
while (dn != dp->df_next) {
dpl = dn->df_next;
xfree(dn->charmatchp);
xfree(dn);
dn = dpl;
}
@ -1753,11 +1766,18 @@ void diff_clear(tabpage_T *tp)
diff_T *next_p;
for (diff_T *p = tp->tp_first_diff; p != NULL; p = next_p) {
next_p = p->df_next;
xfree(p->charmatchp);
xfree(p);
}
tp->tp_first_diff = NULL;
}
/// Return true if char diff option is enabled.
bool chardiff(void)
{
return (diff_flags & DIFF_CHARDIFF) || (diff_flags & DIFF_WORDDIFF);
}
/// Return true if the options are set to use diff linematch.
bool diff_linematch(diff_T *dp)
{
@ -2001,13 +2021,23 @@ static void apply_linematch_results(diff_T *dp, size_t decisions_length, const i
dp->is_linematched = true;
}
static void run_linematch_algorithm(diff_T *dp)
static void run_alignment_algorithm(diff_T *dp, diff_allignment_T diff_allignment)
{
// define buffers for diff algorithm
mmfile_t diffbufs_mm[DB_COUNT];
const char *diffbufs[DB_COUNT];
int diff_length[DB_COUNT];
mmfile_t diffbufs_mm[DB_COUNT] = { 0 };
char *diffbufs[DB_COUNT] = { 0 };
int diff_length[DB_COUNT] = { 0 };
int diff_lines[DB_COUNT] = { 0 };
size_t ndiffs = 0;
size_t total_word_count = 0;
size_t total_chars_length = 0;
size_t *word_offset_size[DB_COUNT] = { 0 }; // mapping array used for charmatch
size_t *word_offset[DB_COUNT] = { 0 }; // mapping array used for charmatch
size_t word_offset_result_index[DB_COUNT] = { 0 }; // mapping array used for charmatch
size_t *iwhite_index_offset = NULL; // mapping array used for charmatch
size_t result_diff_start_pos[DB_COUNT]; // the position in the result array where this
// an array for index mapping with iwhite
const bool iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0;
for (int i = 0; i < DB_COUNT; i++) {
if (curtab->tp_diffbuf[i] != NULL) {
// write the contents of the entire buffer to
@ -2019,28 +2049,216 @@ static void run_linematch_algorithm(diff_T *dp)
// we add it to the array of char*, diffbufs
diffbufs[ndiffs] = diffbufs_mm[ndiffs].ptr;
diff_lines[ndiffs] = dp->df_count[i];
if (diff_allignment == CHARMATCH || diff_allignment == WORDMATCH) {
// before removing whitespace for charmatch
result_diff_start_pos[ndiffs] = total_chars_length;
// get the length of each of the diffs
int lines = dp->df_count[i];
const char *p = diffbufs[ndiffs];
while (lines) {
total_chars_length++; // increment the total characters counter
if (*p == '\n') {
lines--;
}
p++;
}
} else if (diff_allignment == LINEMATCH) {
// LINEMATCH
// keep track of the length of this diff block to pass it to the linematch
// algorithm
diff_length[ndiffs] = dp->df_count[i];
}
// increment the amount of diff buffers we are passing to the algorithm
ndiffs++;
}
}
// we will get the output of the linematch algorithm in the format of an array
// of integers (*decisions) and the length of that array (decisions_length)
int *decisions = NULL;
const bool iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0;
size_t decisions_length = linematch_nbuffers(diffbufs, diff_length, ndiffs, &decisions, iwhite);
// allocate all the memory we will need to keep track of tokens positions and their respective
// lengths. For word matching, this is the 'word' as vim defines it, for character matching, the
// token is the one or more 8 bit 'chars' that make up a utf character
if (diff_allignment == WORDMATCH || diff_allignment == CHARMATCH) {
// are we ignoring whitespace in the comparison?
if (iwhite) {
// allocate array for index mapping of result array
iwhite_index_offset = xmalloc(total_chars_length * sizeof(size_t));
}
for (size_t i = 0; i < ndiffs; i++) {
XFREE_CLEAR(diffbufs_mm[i].ptr);
word_offset[i] = xmalloc(total_chars_length * sizeof(size_t));
word_offset_size[i] = xmalloc(total_chars_length * sizeof(size_t));
for (size_t j = 0; j < total_chars_length; j++) {
word_offset_size[i][j] = 0;
}
}
}
// calculate the token lengths and white space offset and pre process the contents of the diffs to
// remove white space if necessary
for (size_t i = 0; i < ndiffs; i++) {
int cls = INT_MIN; // keep track of what type of character this is, to determine when we are
// moving to a different word
size_t j = 0; // j will iterate over each character in each of the diffs
size_t k = 0; // k represents the index of the current character if there were no white spaces,
// so we will use k and j to calculate the white space offset and use it later to
// populate the final results for drawing to the screen
// if 'iwhite' is not used, k will always be the same as j
size_t lines = (size_t)diff_lines[i]; // we iterate over each line of this part of the diff
size_t w = result_diff_start_pos[i]; // keep track of the offset of all the characters without
// any whitespace, so that we can ignore the white space
// while calculating the diff, and then use this to
// populate the results
size_t cur_char_length = 0;
while (lines > 0) {
if (iwhite && (diffbufs[i][j] == ' ' || diffbufs[i][j] == '\t')) {
// we are using 'iwhite' and this is a whitespace, so it will not be included as a token in
// the diff algorithm
// we are ignoring whitespace and this is a whitespace ' ' or '\t' reset the class definition
cls = INT_MIN;
} else {
// we have a character which is not a blank (or we are not using iwhite)
// how we determine when there is a new token depends on if this is chardiff or worddiff
if (diff_allignment == WORDMATCH) {
// WORDMATCH
if (utf_class(diffbufs[i][j]) != cls || diffbufs[i][j] == '\n') {
// this is a new token
word_offset[i][diff_length[i]] = k; // mark the offset of this without whitespace
diff_length[i]++; // this diff length has another token, so it gets longer
total_word_count++;
}
cls = utf_class(diffbufs[i][j]);
word_offset_size[i][diff_length[i] - 1]++; // still the same class (iterating over the
// same type of word), so the current word
// length is getting longer
} else if (diff_allignment == CHARMATCH) {
// CHARMATCH
if (cur_char_length == 0) {
// get the length of current character
if (diffbufs[i][j] == '\n') {
// this is the last character of the line
cur_char_length = 1;
} else {
cur_char_length = (size_t)utfc_ptr2len((const char *const)&diffbufs[i][j]);
}
word_offset[i][diff_length[i]] = k;
diff_length[i]++;
total_word_count++;
// the token size is the length of this utf character
word_offset_size[i][diff_length[i] - 1] = cur_char_length;
}
cur_char_length--;
}
// if ignoring whitespace, keep track of the white space index
if (iwhite && (diff_allignment == CHARMATCH || diff_allignment == WORDMATCH)) {
// keep track of the index offset with ignoring whitespace to use when populating the results
iwhite_index_offset[w++] = j - k;
}
diffbufs[i][k++] = diffbufs[i][j];
}
if (diffbufs[i][j++] == '\n') {
lines--;
}
}
}
// we will get the output of the linematch algorithm in the format of an array
// of integers (*decisions) and the length of that array (decisions_length)
if (diff_allignment == LINEMATCH) {
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers((const char **)diffbufs, diff_length, ndiffs,
&decisions, 0, NULL, NULL);
apply_linematch_results(dp, decisions_length, decisions);
xfree(decisions);
} else if (diff_allignment == CHARMATCH || diff_allignment == WORDMATCH) {
dp->charmatchp = xmalloc(total_chars_length * sizeof(int)); // will hold results
dp->n_charmatch = total_chars_length;
bool lim_exceeded = false;
if (diff_allignment == CHARMATCH && total_chars_length > (size_t)chardiff_chars) {
lim_exceeded = true;
} else if (diff_allignment == WORDMATCH && total_word_count > (size_t)worddiff_words) {
lim_exceeded = true;
}
if (lim_exceeded == true) {
// do not run charmatch on the entire diff block
// we will attempt to run charmatch on the individual lines later
// for now, just initialize the result memory
for (size_t i = 0; i < total_chars_length; i++) {
dp->charmatchp[i] = -1; // -1 indicates that algorithm has not yet ran
}
} else {
for (size_t i = 0; i < total_chars_length; i++) {
dp->charmatchp[i] = 0; // default to not highlighted
}
// check is this a line that does not exist in other buffers?
// if so, highlight it as a 'newline', and we don't need to run the algorithm
bool newline = true;
for (size_t i = 0, c = 0; i < ndiffs; i++) {
if (diff_length[i] > 0) {
c++;
}
if (c > 1) {
newline = false;
break;
}
}
if (newline == true) {
for (size_t i = 0; i < total_chars_length; i++) {
dp->charmatchp[i] = 2;
}
} else {
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers((const char **)diffbufs, diff_length, ndiffs,
&decisions, 1, word_offset, word_offset_size);
for (size_t i = 0; i < decisions_length; i++) {
if (decisions[i] == (pow(2, (double)ndiffs) - 1)) {
// it's a comparison of all the buffers (don't highlight)
for (size_t j = 0; j < ndiffs; j++) {
for (size_t k = 0;
k <
(diff_allignment ==
WORDMATCH ? word_offset_size[j][word_offset_result_index[j]] : 1); k++) {
size_t l = result_diff_start_pos[j]++;
dp->charmatchp[iwhite_index_offset ? iwhite_index_offset[l] + l : l] = 0;
}
word_offset_result_index[j]++;
}
} else {
// it's a skip in a single buffer (highlight as changed)
for (size_t j = 0; j < ndiffs; j++) {
if (decisions[i] & (1 << j)) {
for (size_t k = 0;
k <
(diff_allignment ==
WORDMATCH ? word_offset_size[j][word_offset_result_index[j]] : 1); k++) {
size_t l = result_diff_start_pos[j]++;
dp->charmatchp[iwhite_index_offset ? iwhite_index_offset[l] + l : l] = 1;
}
word_offset_result_index[j]++;
break;
}
}
}
}
xfree(decisions);
}
}
}
for (size_t i = 0; i < ndiffs; i++) {
xfree(word_offset[i]);
xfree(word_offset_size[i]);
XFREE_CLEAR(diffbufs_mm[i].ptr);
}
xfree(iwhite_index_offset);
}
/// Check diff status for line "lnum" in buffer "buf":
@ -2105,7 +2323,7 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
// above the screen.
if (lnum >= wp->w_topline && lnum < wp->w_botline
&& !dp->is_linematched && diff_linematch(dp)) {
run_linematch_algorithm(dp);
run_alignment_algorithm(dp, LINEMATCH);
}
if (dp->is_linematched) {
@ -2412,6 +2630,8 @@ int diffopt_changed(void)
{
int diff_context_new = 6;
int linematch_lines_new = 0;
int chardiff_chars_new = 0;
int worddiff_words_new = 0;
int diff_flags_new = 0;
int diff_foldcolumn_new = 2;
int diff_algorithm_new = 0;
@ -2487,6 +2707,14 @@ int diffopt_changed(void)
p += 10;
linematch_lines_new = getdigits_int(&p, false, linematch_lines_new);
diff_flags_new |= DIFF_LINEMATCH;
} else if ((strncmp(p, "chardiff:", 9) == 0) && ascii_isdigit(p[9])) {
p += 9;
chardiff_chars_new = getdigits_int(&p, false, chardiff_chars_new);
diff_flags_new |= DIFF_CHARDIFF;
} else if ((strncmp(p, "worddiff:", 9) == 0) && ascii_isdigit(p[9])) {
p += 9;
worddiff_words_new = getdigits_int(&p, false, worddiff_words_new);
diff_flags_new |= DIFF_WORDDIFF;
}
if ((*p != ',') && (*p != NUL)) {
@ -2516,6 +2744,8 @@ int diffopt_changed(void)
diff_flags = diff_flags_new;
diff_context = diff_context_new == 0 ? 1 : diff_context_new;
linematch_lines = linematch_lines_new;
chardiff_chars = chardiff_chars_new;
worddiff_words = worddiff_words_new;
diff_foldcolumn = diff_foldcolumn_new;
diff_algorithm = diff_algorithm_new;
@ -2563,7 +2793,8 @@ bool diffopt_filler(void)
/// @param endp last char of the change
///
/// @return true if the line was added, no other buffer has it.
bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp, int **hlresult,
bool *diffchars_lim_exceeded, size_t *diffchars_line_len)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
// Make a copy of the line, the next ml_get() will invalidate it.
@ -2604,6 +2835,97 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
bool added = true;
linenr_T off = lnum - dp->df_lnum[idx];
if (chardiff()) {
diff_allignment_T diff_allignment;
if (diff_flags & DIFF_CHARDIFF) {
// if both chardiff & worddiff are enabled, it will pick chardiff
diff_allignment = CHARMATCH;
} else if (diff_flags & DIFF_WORDDIFF) {
diff_allignment = WORDMATCH;
}
if (dp->charmatchp == NULL) {
// get the first buffers
// try running on the whole diff buffer first
run_alignment_algorithm(dp, diff_allignment);
}
size_t charcount = 0;
for (int i = 0; i < DB_COUNT; i++) {
// for each diff buffer
if (curtab->tp_diffbuf[i] != NULL) {
for (int j = 0; j < dp->df_count[i]; j++) {
// for each line in that buffer
// get a pointer to the line
char *diffline = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + j);
while (*diffline != '\0') {
diffline++; charcount++;
}
charcount++;
}
}
}
if (dp->n_charmatch != charcount) {
// we need to re run if the length of the diff has changed
// count the number of characters in this diff
// the line is currently being edited in insert mode, so pause highlighting until the diff is
// recalculated, then resume the charmatch highlighting
(*hlresult) = NULL;
} else {
// charmatchp is not null, is the whole thing already diffed?
// get the correct offset for hlresult
//
// if the character count is not null
size_t hlresult_line_offset = 0;
// get the offset for the highlight of this line
*diffchars_line_len = strlen(ml_get_buf(curtab->tp_diffbuf[idx], dp->df_lnum[idx] + off));
hlresult_line_offset = get_buffer_position(idx, dp, off);
if (*(dp->charmatchp + hlresult_line_offset) == -1) {
diff_T dp_tmp;
for (int i = 0; i < DB_COUNT; i++) {
if (curtab->tp_diffbuf[i] != NULL) {
dp_tmp.df_lnum[i] = dp->df_lnum[i] + off;
dp_tmp.df_count[i] = off < dp->df_count[i] ? 1 : 0;
}
}
// this line has not yet been calculated
// run charmatch on this line of the diff
// figure out how many buffers we are diffing
// what line number is this in each buffer?
run_alignment_algorithm(&dp_tmp, diff_allignment);
if (dp_tmp.n_charmatch > 0) {
for (int i = 0, p = 0; i < DB_COUNT; i++) {
if (curtab->tp_diffbuf[i] != NULL) {
// get the offset in the original charmatchp
if (off < dp->df_count[i]) {
size_t length = strlen(ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off)) + 1;
size_t k = get_buffer_position(i, dp, off);
for (size_t m = 0; m < length; m++) {
int val = dp_tmp.charmatchp[p++];
dp->charmatchp[k + m] = val == -1 ? -2 : val; // if this individual line is still
// too long to diff, mark it as a
// -2, meaning it's been attempted
// already
}
}
}
}
// extract the results from here
}
xfree(dp_tmp.charmatchp);
}
(*hlresult) = dp->charmatchp + hlresult_line_offset;
}
if ((*hlresult) == NULL) {
xfree(line_org);
return false;
} else if ((*hlresult)[0] != -2) { // -2 indicates that we've attempted a character wise diff with the
xfree(line_org);
return false; // entire block, and with this individual line, and still exceeded
} else { // the character limit
//
*diffchars_lim_exceeded = true; // go to the default highlighting behaviour without character
} // wise matching
}
for (int i = 0; i < DB_COUNT; i++) {
if ((curtab->tp_diffbuf[i] != NULL) && (i != idx)) {
// Skip lines that are not in the other change (filler lines).
@ -3440,3 +3762,25 @@ static int xdiff_out(int start_a, int count_a, int start_b, int count_b, void *p
}));
return 0;
}
// get the position in the character diff buffer of this line
static size_t get_buffer_position(const int idx, const diff_T *dp, linenr_T offset)
{
size_t comparison_mem_offset = 0;
for (int i = 0; i < DB_COUNT; i++) {
if ((curtab->tp_diffbuf[i] != NULL)) {
for (int j = 0; j < ((i == idx) ? offset : dp->df_count[i]); j++) {
char *diffline = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + j);
while (*diffline != '\0') {
diffline++; comparison_mem_offset++;
}
comparison_mem_offset++; // count the '\0' character as the newline marker for each line
}
if (i == idx) {
break;
}
}
}
// what is the line length for this pointer?
return comparison_mem_offset;
}

View File

@ -170,28 +170,26 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
// cache previous calculations depending on w_virtcol
static int saved_w_virtcol;
static win_T *prev_wp;
static int prev_width1;
static int prev_width2;
static int prev_left_col;
static int prev_right_col;
static int prev_col_off;
int cur_col_off = win_col_off(wp);
int width1;
int width2;
int width1 = wp->w_width_inner - cur_col_off;
int width2 = width1 + win_col_off2(wp);
if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp
&& prev_col_off == cur_col_off) {
&& prev_width1 == width1 && prev_width2 == width2) {
*right_col = prev_right_col;
*left_col = prev_left_col;
return;
}
width1 = wp->w_width_inner - cur_col_off;
width2 = width1 + win_col_off2(wp);
*left_col = 0;
*right_col = width1;
if (wp->w_virtcol >= (colnr_T)width1) {
if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
*right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2;
}
if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
@ -202,8 +200,9 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
prev_left_col = *left_col;
prev_right_col = *right_col;
prev_wp = wp;
prev_width1 = width1;
prev_width2 = width2;
saved_w_virtcol = wp->w_virtcol;
prev_col_off = cur_col_off;
}
/// Put a single char from an UTF-8 buffer into a line buffer.
@ -1137,10 +1136,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
int bg_attr = win_bg_attr(wp);
int linestatus = 0;
bool diffchars_lim_exceeded = false;
size_t diffchars_line_len = 0;
int *hlresult = NULL;
wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
if (wlv.filler_lines < 0 || linestatus < 0) {
if (wlv.filler_lines == -1 || linestatus == -1) {
if (diff_find_change(wp, lnum, &change_start, &change_end)) {
if (diff_find_change(wp, lnum, &change_start, &change_end, &hlresult,
&diffchars_lim_exceeded, &diffchars_line_len)) {
wlv.diff_hlf = HLF_ADD; // added line
} else if (change_start == 0) {
wlv.diff_hlf = HLF_TXD; // changed text
@ -1721,6 +1724,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
}
if (wlv.diff_hlf != (hlf_T)0) {
if (chardiff() && !diffchars_lim_exceeded) {
if (wlv.diff_hlf != HLF_ADD) {
if (hlresult == NULL) {
wlv.diff_hlf = HLF_CHD;
} else if (hlresult[0] == 2) {
wlv.diff_hlf = HLF_ADD;
} else if ((size_t)(ptr - line) < diffchars_line_len
&& (hlresult[ptr - line] == 1 || hlresult[ptr - line] == -2)) {
wlv.diff_hlf = HLF_TXD;
} else {
wlv.diff_hlf = HLF_CHD;
}
}
} else {
// When there is extra text (eg: virtual text) it gets the
// diff highlighting for the line, but not for changed text.
if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start
@ -1731,6 +1748,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|| (wlv.n_extra > 0 && wlv.extra_for_extmark))) {
wlv.diff_hlf = HLF_CHD; // changed line
}
}
wlv.line_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
// Overlay CursorLine onto diff-mode highlight.
if (wlv.cul_attr) {

View File

@ -32,7 +32,7 @@
#include "nvim/eval/vars.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
@ -8506,7 +8506,7 @@ Channel *find_job(uint64_t id, bool show_error)
{
Channel *data = find_channel(id);
if (!data || data->streamtype != kChannelStreamProc
|| process_is_stopped(&data->stream.proc)) {
|| proc_is_stopped(&data->stream.proc)) {
if (show_error) {
if (data && data->streamtype != kChannelStreamProc) {
emsg(_(e_invchanjob));

View File

@ -8044,7 +8044,7 @@ M.funcs = {
]=],
name = 'prompt_getprompt',
params = { { 'buf', 'any' } },
params = { { 'buf', 'integer|string' } },
signature = 'prompt_getprompt({buf})',
},
prompt_setcallback = {
@ -8084,7 +8084,7 @@ M.funcs = {
]=],
name = 'prompt_setcallback',
params = { { 'buf', 'any' }, { 'expr', 'any' } },
params = { { 'buf', 'integer|string' }, { 'expr', 'string|function' } },
signature = 'prompt_setcallback({buf}, {expr})',
},
prompt_setinterrupt = {
@ -8101,7 +8101,7 @@ M.funcs = {
]=],
name = 'prompt_setinterrupt',
params = { { 'buf', 'any' }, { 'expr', 'any' } },
params = { { 'buf', 'integer|string' }, { 'expr', 'string|function' } },
signature = 'prompt_setinterrupt({buf}, {expr})',
},
prompt_setprompt = {
@ -8116,7 +8116,7 @@ M.funcs = {
<
]=],
name = 'prompt_setprompt',
params = { { 'buf', 'any' }, { 'text', 'any' } },
params = { { 'buf', 'integer|string' }, { 'text', 'string' } },
signature = 'prompt_setprompt({buf}, {text})',
},
pum_getpos = {

View File

@ -49,7 +49,7 @@
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
@ -101,7 +101,7 @@
#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/pty_proc.h"
#include "nvim/os/shell.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/os/time.h"
@ -1304,6 +1304,9 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static int change_start = 0;
static int change_end = 0;
static hlf_T hlID = (hlf_T)0;
bool diffchars_lim_exceeded = false;
size_t diffchars_line_len = 0;
int *hlresult = NULL;
if (lnum < 0) { // ignore type error in {lnum} arg
lnum = 0;
@ -1318,7 +1321,8 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (filler_lines == -1 || linestatus == -1) {
change_start = MAXCOL;
change_end = -1;
if (diff_find_change(curwin, lnum, &change_start, &change_end)) {
if (diff_find_change(curwin, lnum, &change_start, &change_end, &hlresult,
&diffchars_lim_exceeded, &diffchars_line_len)) {
hlID = HLF_ADD; // added line
} else {
hlID = HLF_CHD; // changed line
@ -1336,12 +1340,28 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (hlID == HLF_CHD || hlID == HLF_TXD) {
int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
//
if (chardiff() && !diffchars_lim_exceeded) {
if (hlID != HLF_ADD) {
if (hlresult == NULL) {
hlID = HLF_CHD;
} else if (hlresult[0] == 2) {
hlID = HLF_ADD;
} else if ((size_t)col < diffchars_line_len
&& (hlresult[col] == 1 || hlresult[col] == -2)) {
hlID = HLF_TXD;
} else {
hlID = HLF_CHD;
}
}
} else {
if (col >= change_start && col <= change_end) {
hlID = HLF_TXD; // Changed text.
} else {
hlID = HLF_CHD; // Changed line.
}
}
}
rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (hlID + 1);
}
@ -3770,7 +3790,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
Process *proc = &data->stream.proc;
Proc *proc = &data->stream.proc;
rettv->vval.v_number = proc->pid;
}
@ -3796,12 +3816,12 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
if (data->stream.proc.type != kProcessTypePty) {
if (data->stream.proc.type != kProcTypePty) {
emsg(_(e_channotpty));
return;
}
pty_process_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number,
pty_proc_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number,
(uint16_t)argvars[2].vval.v_number);
rettv->vval.v_number = 1;
}
@ -4077,7 +4097,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Ignore return code, but show error later.
channel_close(data->id, kChannelPartRpc, &error);
}
process_stop(&data->stream.proc);
proc_stop(&data->stream.proc);
rettv->vval.v_number = 1;
if (error) {
emsg(error);
@ -4113,10 +4133,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|| !(chan = find_channel((uint64_t)TV_LIST_ITEM_TV(arg)->vval.v_number))
|| chan->streamtype != kChannelStreamProc) {
jobs[i] = NULL; // Invalid job.
} else if (process_is_stopped(&chan->stream.proc)) {
} else if (proc_is_stopped(&chan->stream.proc)) {
// Job is stopped but not fully destroyed.
// Ensure all callbacks on its event queue are executed. #15402
process_wait(&chan->stream.proc, -1, NULL);
proc_wait(&chan->stream.proc, -1, NULL);
jobs[i] = NULL; // Invalid job.
} else {
jobs[i] = chan;
@ -4144,7 +4164,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (jobs[i] == NULL) {
continue; // Invalid job, will assign status=-3 below.
}
int status = process_wait(&jobs[i]->stream.proc, remaining,
int status = proc_wait(&jobs[i]->stream.proc, remaining,
waiting_jobs);
if (status < 0) {
break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
@ -8207,7 +8227,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
int pid = chan->stream.pty.process.pid;
int pid = chan->stream.pty.proc.pid;
// "./…" => "/home/foo/…"
vim_FullName(cwd, NameBuff, sizeof(NameBuff), false);

View File

@ -142,30 +142,31 @@ struct socket_watcher {
};
typedef enum {
kProcessTypeUv,
kProcessTypePty,
} ProcessType;
kProcTypeUv,
kProcTypePty,
} ProcType;
typedef struct process Process;
typedef void (*process_exit_cb)(Process *proc, int status, void *data);
typedef void (*internal_process_cb)(Process *proc);
/// OS process
typedef struct proc Proc;
typedef void (*proc_exit_cb)(Proc *proc, int status, void *data);
typedef void (*internal_proc_cb)(Proc *proc);
struct process {
ProcessType type;
struct proc {
ProcType type;
Loop *loop;
void *data;
int pid, status, refcount;
uint8_t exit_signal; // Signal used when killing (on Windows).
uint64_t stopped_time; // process_stop() timestamp
uint64_t stopped_time; // proc_stop() timestamp
const char *cwd;
char **argv;
const char *exepath;
dict_T *env;
Stream in;
RStream out, err;
/// Exit handler. If set, user must call process_free().
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
/// Exit handler. If set, user must call proc_free().
proc_exit_cb cb;
internal_proc_cb internal_exit_cb, internal_close_cb;
bool closed, detach, overlapped, fwd_err;
MultiQueue *events;
};

View File

@ -5,9 +5,9 @@
#include "nvim/eval/typval.h"
#include "nvim/event/defs.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/libuv_proc.h"
#include "nvim/event/loop.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/log.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
@ -15,15 +15,15 @@
#include "nvim/ui_client.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/libuv_process.c.generated.h"
# include "event/libuv_proc.c.generated.h"
#endif
/// @returns zero on success, or negative error code
int libuv_process_spawn(LibuvProcess *uvproc)
int libuv_proc_spawn(LibuvProc *uvproc)
FUNC_ATTR_NONNULL_ALL
{
Process *proc = (Process *)uvproc;
uvproc->uvopts.file = process_get_exepath(proc);
Proc *proc = (Proc *)uvproc;
uvproc->uvopts.file = proc_get_exepath(proc);
uvproc->uvopts.args = proc->argv;
uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
#ifdef MSWIN
@ -101,7 +101,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
return status;
}
void libuv_process_close(LibuvProcess *uvproc)
void libuv_proc_close(LibuvProc *uvproc)
FUNC_ATTR_NONNULL_ARG(1)
{
uv_close((uv_handle_t *)&uvproc->uv, close_cb);
@ -109,11 +109,11 @@ void libuv_process_close(LibuvProcess *uvproc)
static void close_cb(uv_handle_t *handle)
{
Process *proc = handle->data;
Proc *proc = handle->data;
if (proc->internal_close_cb) {
proc->internal_close_cb(proc);
}
LibuvProcess *uvproc = (LibuvProcess *)proc;
LibuvProc *uvproc = (LibuvProc *)proc;
if (uvproc->uvopts.env) {
os_free_fullenv(uvproc->uvopts.env);
}
@ -121,7 +121,7 @@ static void close_cb(uv_handle_t *handle)
static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
{
Process *proc = handle->data;
Proc *proc = handle->data;
#if defined(MSWIN)
// Use stored/expected signal.
term_signal = proc->exit_signal;
@ -130,10 +130,10 @@ static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
proc->internal_exit_cb(proc);
}
LibuvProcess libuv_process_init(Loop *loop, void *data)
LibuvProc libuv_proc_init(Loop *loop, void *data)
{
LibuvProcess rv = {
.process = process_init(loop, kProcessTypeUv, data)
LibuvProc rv = {
.proc = proc_init(loop, kProcTypeUv, data)
};
return rv;
}

View File

@ -5,12 +5,12 @@
#include "nvim/event/defs.h"
typedef struct {
Process process;
Proc proc;
uv_process_t uv;
uv_process_options_t uvopts;
uv_stdio_container_t uvstdio[4];
} LibuvProcess;
} LibuvProc;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/libuv_process.h.generated.h"
# include "event/libuv_proc.h.generated.h"
#endif

View File

@ -4,24 +4,24 @@
#include <uv.h>
#include "klib/klist.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/libuv_proc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
#include "nvim/os/process.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/proc.h"
#include "nvim/os/pty_proc.h"
#include "nvim/os/shell.h"
#include "nvim/os/time.h"
#include "nvim/ui_client.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/process.c.generated.h"
# include "event/proc.c.generated.h"
#endif
// Time for a process to exit cleanly before we send KILL.
@ -33,13 +33,13 @@
void __gcov_flush(void);
#endif
static bool process_is_tearing_down = false;
static bool proc_is_tearing_down = false;
// Delay exit until handles are closed, to avoid deadlocks
static int exit_need_delay = 0;
/// @returns zero on success, or negative error code
int process_spawn(Process *proc, bool in, bool out, bool err)
int proc_spawn(Proc *proc, bool in, bool out, bool err)
FUNC_ATTR_NONNULL_ALL
{
// forwarding stderr contradicts with processing it internally
@ -70,11 +70,11 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
int status;
switch (proc->type) {
case kProcessTypeUv:
status = libuv_process_spawn((LibuvProcess *)proc);
case kProcTypeUv:
status = libuv_proc_spawn((LibuvProc *)proc);
break;
case kProcessTypePty:
status = pty_process_spawn((PtyProcess *)proc);
case kProcTypePty:
status = pty_proc_spawn((PtyProc *)proc);
break;
}
@ -89,12 +89,12 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
uv_close((uv_handle_t *)&proc->err.s.uv.pipe, NULL);
}
if (proc->type == kProcessTypeUv) {
uv_close((uv_handle_t *)&(((LibuvProcess *)proc)->uv), NULL);
if (proc->type == kProcTypeUv) {
uv_close((uv_handle_t *)&(((LibuvProc *)proc)->uv), NULL);
} else {
process_close(proc);
proc_close(proc);
}
process_free(proc);
proc_free(proc);
proc->status = -1;
return status;
}
@ -102,52 +102,52 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
if (in) {
stream_init(NULL, &proc->in, -1, (uv_stream_t *)&proc->in.uv.pipe);
proc->in.internal_data = proc;
proc->in.internal_close_cb = on_process_stream_close;
proc->in.internal_close_cb = on_proc_stream_close;
proc->refcount++;
}
if (out) {
stream_init(NULL, &proc->out.s, -1, (uv_stream_t *)&proc->out.s.uv.pipe);
proc->out.s.internal_data = proc;
proc->out.s.internal_close_cb = on_process_stream_close;
proc->out.s.internal_close_cb = on_proc_stream_close;
proc->refcount++;
}
if (err) {
stream_init(NULL, &proc->err.s, -1, (uv_stream_t *)&proc->err.s.uv.pipe);
proc->err.s.internal_data = proc;
proc->err.s.internal_close_cb = on_process_stream_close;
proc->err.s.internal_close_cb = on_proc_stream_close;
proc->refcount++;
}
proc->internal_exit_cb = on_process_exit;
proc->internal_exit_cb = on_proc_exit;
proc->internal_close_cb = decref;
proc->refcount++;
kl_push(WatcherPtr, proc->loop->children, proc);
DLOG("new: pid=%d exepath=[%s]", proc->pid, process_get_exepath(proc));
DLOG("new: pid=%d exepath=[%s]", proc->pid, proc_get_exepath(proc));
return 0;
}
void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
void proc_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
{
process_is_tearing_down = true;
proc_is_tearing_down = true;
kl_iter(WatcherPtr, loop->children, current) {
Process *proc = (*current)->data;
if (proc->detach || proc->type == kProcessTypePty) {
Proc *proc = (*current)->data;
if (proc->detach || proc->type == kProcTypePty) {
// Close handles to process without killing it.
CREATE_EVENT(loop->events, process_close_handles, proc);
CREATE_EVENT(loop->events, proc_close_handles, proc);
} else {
process_stop(proc);
proc_stop(proc);
}
}
// Wait until all children exit and all close events are processed.
LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1,
kl_empty(loop->children) && multiqueue_empty(loop->events));
pty_process_teardown(loop);
pty_proc_teardown(loop);
}
void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL
void proc_close_streams(Proc *proc) FUNC_ATTR_NONNULL_ALL
{
wstream_may_close(&proc->in);
rstream_may_close(&proc->out);
@ -162,7 +162,7 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL
/// @return Exit code of the process. proc->status will have the same value.
/// -1 if the timeout expired while the process is still running.
/// -2 if the user interrupted the wait.
int process_wait(Process *proc, int ms, MultiQueue *events)
int proc_wait(Proc *proc, int ms, MultiQueue *events)
FUNC_ATTR_NONNULL_ARG(1)
{
if (!proc->refcount) {
@ -186,7 +186,7 @@ int process_wait(Process *proc, int ms, MultiQueue *events)
// Assume that a user hitting CTRL-C does not like the current job. Kill it.
if (got_int) {
got_int = false;
process_stop(proc);
proc_stop(proc);
if (ms == -1) {
// We can only return if all streams/handles are closed and the job
// exited.
@ -214,7 +214,7 @@ int process_wait(Process *proc, int ms, MultiQueue *events)
}
/// Ask a process to terminate and eventually kill if it doesn't respond
void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
void proc_stop(Proc *proc) FUNC_ATTR_NONNULL_ALL
{
bool exited = (proc->status >= 0);
if (exited || proc->stopped_time) {
@ -224,13 +224,13 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
proc->exit_signal = SIGTERM;
switch (proc->type) {
case kProcessTypeUv:
case kProcTypeUv:
os_proc_tree_kill(proc->pid, SIGTERM);
break;
case kProcessTypePty:
case kProcTypePty:
// close all streams for pty processes to send SIGHUP to the process
process_close_streams(proc);
pty_process_close_master((PtyProcess *)proc);
proc_close_streams(proc);
pty_proc_close_master((PtyProc *)proc);
break;
}
@ -240,7 +240,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
}
/// Frees process-owned resources.
void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL
void proc_free(Proc *proc) FUNC_ATTR_NONNULL_ALL
{
if (proc->argv != NULL) {
shell_free_argv(proc->argv);
@ -249,19 +249,19 @@ void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL
}
/// Sends SIGKILL (or SIGTERM..SIGKILL for PTY jobs) to processes that did
/// not terminate after process_stop().
/// not terminate after proc_stop().
static void children_kill_cb(uv_timer_t *handle)
{
Loop *loop = handle->loop->data;
kl_iter(WatcherPtr, loop->children, current) {
Process *proc = (*current)->data;
Proc *proc = (*current)->data;
bool exited = (proc->status >= 0);
if (exited || !proc->stopped_time) {
continue;
}
uint64_t term_sent = UINT64_MAX == proc->stopped_time;
if (kProcessTypePty != proc->type || term_sent) {
if (kProcTypePty != proc->type || term_sent) {
proc->exit_signal = SIGKILL;
os_proc_tree_kill(proc->pid, SIGKILL);
} else {
@ -275,19 +275,19 @@ static void children_kill_cb(uv_timer_t *handle)
}
}
static void process_close_event(void **argv)
static void proc_close_event(void **argv)
{
Process *proc = argv[0];
Proc *proc = argv[0];
if (proc->cb) {
// User (hint: channel_job_start) is responsible for calling
// process_free().
// proc_free().
proc->cb(proc, proc->status, proc->data);
} else {
process_free(proc);
proc_free(proc);
}
}
static void decref(Process *proc)
static void decref(Proc *proc)
{
if (--proc->refcount != 0) {
return;
@ -303,13 +303,13 @@ static void decref(Process *proc)
}
assert(node);
kl_shift_at(WatcherPtr, loop->children, node);
CREATE_EVENT(proc->events, process_close_event, proc);
CREATE_EVENT(proc->events, proc_close_event, proc);
}
static void process_close(Process *proc)
static void proc_close(Proc *proc)
FUNC_ATTR_NONNULL_ARG(1)
{
if (process_is_tearing_down && (proc->detach || proc->type == kProcessTypePty)
if (proc_is_tearing_down && (proc->detach || proc->type == kProcTypePty)
&& proc->closed) {
// If a detached/pty process dies while tearing down it might get closed
// twice.
@ -319,17 +319,17 @@ static void process_close(Process *proc)
proc->closed = true;
if (proc->detach) {
if (proc->type == kProcessTypeUv) {
uv_unref((uv_handle_t *)&(((LibuvProcess *)proc)->uv));
if (proc->type == kProcTypeUv) {
uv_unref((uv_handle_t *)&(((LibuvProc *)proc)->uv));
}
}
switch (proc->type) {
case kProcessTypeUv:
libuv_process_close((LibuvProcess *)proc);
case kProcTypeUv:
libuv_proc_close((LibuvProc *)proc);
break;
case kProcessTypePty:
pty_process_close((PtyProcess *)proc);
case kProcTypePty:
pty_proc_close((PtyProc *)proc);
break;
}
}
@ -338,7 +338,7 @@ static void process_close(Process *proc)
///
/// @param proc Process, for which an output stream should be flushed.
/// @param stream Stream to flush.
static void flush_stream(Process *proc, RStream *stream)
static void flush_stream(Proc *proc, RStream *stream)
FUNC_ATTR_NONNULL_ARG(1)
{
if (!stream || stream->s.closed) {
@ -382,16 +382,16 @@ static void flush_stream(Process *proc, RStream *stream)
}
}
static void process_close_handles(void **argv)
static void proc_close_handles(void **argv)
{
Process *proc = argv[0];
Proc *proc = argv[0];
exit_need_delay++;
flush_stream(proc, &proc->out);
flush_stream(proc, &proc->err);
process_close_streams(proc);
process_close(proc);
proc_close_streams(proc);
proc_close(proc);
exit_need_delay--;
}
@ -426,7 +426,7 @@ void exit_from_channel(int status)
multiqueue_put(main_loop.fast_events, exit_event, (void *)(intptr_t)status);
}
static void on_process_exit(Process *proc)
static void on_proc_exit(Proc *proc)
{
Loop *loop = proc->loop;
ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status,
@ -439,13 +439,13 @@ static void on_process_exit(Process *proc)
// Process has terminated, but there could still be data to be read from the
// OS. We are still in the libuv loop, so we cannot call code that polls for
// more data directly. Instead delay the reading after the libuv loop by
// queueing process_close_handles() as an event.
// queueing proc_close_handles() as an event.
MultiQueue *queue = proc->events ? proc->events : loop->events;
CREATE_EVENT(queue, process_close_handles, proc);
CREATE_EVENT(queue, proc_close_handles, proc);
}
static void on_process_stream_close(Stream *stream, void *data)
static void on_proc_stream_close(Stream *stream, void *data)
{
Process *proc = data;
Proc *proc = data;
decref(proc);
}

View File

@ -6,9 +6,9 @@
#include "nvim/event/defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h"
static inline Process process_init(Loop *loop, ProcessType type, void *data)
static inline Proc proc_init(Loop *loop, ProcType type, void *data)
{
return (Process) {
return (Proc) {
.type = type,
.data = data,
.loop = loop,
@ -33,17 +33,17 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
}
/// Get the path to the executable of the process.
static inline const char *process_get_exepath(Process *proc)
static inline const char *proc_get_exepath(Proc *proc)
{
return proc->exepath != NULL ? proc->exepath : proc->argv[0];
}
static inline bool process_is_stopped(Process *proc)
static inline bool proc_is_stopped(Proc *proc)
{
bool exited = (proc->status >= 0);
return exited || (proc->stopped_time != 0);
}
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/process.h.generated.h"
# include "event/proc.h.generated.h"
#endif

View File

@ -38,38 +38,6 @@ static size_t line_len(const char *s)
return strlen(s);
}
/// Same as matching_chars but ignore whitespace
///
/// @param s1
/// @param s2
static int matching_chars_iwhite(const char *s1, const char *s2)
{
// the newly processed strings that will be compared
// delete the white space characters, and/or replace all upper case with lower
char *strsproc[2];
const char *strsorig[2] = { s1, s2 };
for (int k = 0; k < 2; k++) {
size_t d = 0;
size_t i = 0;
size_t slen = line_len(strsorig[k]);
strsproc[k] = xmalloc((slen + 1) * sizeof(char));
while (d + i < slen) {
char e = strsorig[k][i + d];
if (e != ' ' && e != '\t') {
strsproc[k][i] = e;
i++;
} else {
d++;
}
}
strsproc[k][i] = NUL;
}
int matching = matching_chars(strsproc[0], strsproc[1]);
xfree(strsproc[0]);
xfree(strsproc[1]);
return matching;
}
#define MATCH_CHAR_MAX_LEN 800
/// Return matching characters between "s1" and "s2" whilst respecting sequence order.
@ -119,7 +87,7 @@ static int matching_chars(const char *s1, const char *s2)
/// @param sp
/// @param fomvals
/// @param n
static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
static int count_n_matched_chars(const char **sp, const size_t n)
{
int matched_chars = 0;
int matched = 0;
@ -127,9 +95,7 @@ static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
for (size_t j = i + 1; j < n; j++) {
if (sp[i] != NULL && sp[j] != NULL) {
matched++;
// TODO(lewis6991): handle whitespace ignoring higher up in the stack
matched_chars += iwhite ? matching_chars_iwhite(sp[i], sp[j])
: matching_chars(sp[i], sp[j]);
matched_chars += matching_chars(sp[i], sp[j]);
}
}
}
@ -145,7 +111,7 @@ static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
{
for (int i = 0; i < lnum - 1; i++) {
for (long i = 0; i < lnum; i++) {
*s = strchr(*s, '\n');
if (!*s) {
return;
@ -168,20 +134,32 @@ void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
static void try_possible_paths(const int *df_iters, const size_t *paths, const int npaths,
const int path_idx, int *choice, diffcmppath_T *diffcmppath,
const int *diff_len, const size_t ndiffs, const char **diff_blk,
bool iwhite)
bool charmatch, size_t **word_offset, size_t **word_offset_size)
{
if (path_idx == npaths) {
if ((*choice) > 0) {
int from_vals[LN_MAX_BUFS] = { 0 };
const int *to_vals = df_iters;
const char *current_lines[LN_MAX_BUFS];
size_t word_len[LN_MAX_BUFS];
for (size_t k = 0; k < ndiffs; k++) {
from_vals[k] = df_iters[k];
// get the index at all of the places
if ((*choice) & (1 << k)) {
from_vals[k]--;
const char *p = diff_blk[k];
fastforward_buf_to_lnum(&p, df_iters[k]);
if (charmatch) {
if (word_offset[k] != NULL) {
// get start position
p += word_offset[k][df_iters[k] - 1];
word_len[k] = word_offset_size[k][df_iters[k] - 1];
// index of the word for comparison
} else {
p += df_iters[k] - 1; // advance by the character count
}
} else {
fastforward_buf_to_lnum(&p, df_iters[k] - 1);
}
current_lines[k] = p;
} else {
current_lines[k] = NULL;
@ -189,7 +167,42 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
}
size_t unwrapped_idx_from = unwrap_indexes(from_vals, diff_len, ndiffs);
size_t unwrapped_idx_to = unwrap_indexes(to_vals, diff_len, ndiffs);
int matched_chars = count_n_matched_chars(current_lines, ndiffs, iwhite);
int matched_chars = 0;
if (charmatch) {
// only two valid options for charmatch
// 1. skip of a single character
// 2. a combination of 'ndiffs' characters, when not equal to '\n'
char t[256];
t[0] = '\0';
size_t t_l = 0;
size_t compared = 0;
for (size_t i = 0; i < ndiffs; i++) {
if (current_lines[i] != NULL) {
compared++;
if ((t[0] != '\0' && !compare(current_lines[i],
(word_offset[i] != NULL ? word_len[i] : 1), t, t_l)) // if theres more than one to compare, and
// they're not matching
|| (t[0] != '\0' && current_lines[i][0] == '\n')) { // if there's more than one to compare, and
// at least one is a '\n'
return; // not a possible path
} else if (t[0] != '\0') {
matched_chars = 1; // comparison of all buffers
}
size_t this_word_length = word_offset[i] != NULL ? word_len[i] : 1;
t_l = this_word_length;
for (size_t l = 0; l < this_word_length; l++) {
t[l] = current_lines[i][l];
}
}
}
if (!(compared == ndiffs || compared == 1)) {
return;
}
} else {
matched_chars = count_n_matched_chars(current_lines, ndiffs);
}
int score = diffcmppath[unwrapped_idx_from].df_lev_score + matched_chars;
if (score > diffcmppath[unwrapped_idx_to].df_lev_score) {
diffcmppath[unwrapped_idx_to].df_path_n = 1;
@ -207,10 +220,12 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
size_t bit_place = paths[path_idx];
*(choice) |= (1 << bit_place); // set it to 1
try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice,
diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
diffcmppath, diff_len, ndiffs, diff_blk, charmatch, word_offset,
word_offset_size);
*(choice) &= ~(1 << bit_place); // set it to 0
try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice,
diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
diffcmppath, diff_len, ndiffs, diff_blk, charmatch, word_offset,
word_offset_size);
}
/// unwrap indexes to access n dimensional tensor
@ -245,7 +260,7 @@ static size_t unwrap_indexes(const int *values, const int *diff_len, const size_
/// @param diff_blk
static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *diffcmppath,
const int *diff_len, const size_t ndiffs, const char **diff_blk,
bool iwhite)
bool charmatch, size_t **word_offset, size_t **word_offset_size)
{
if (ch_dim == ndiffs) {
int npaths = 0;
@ -259,16 +274,19 @@ static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *d
}
int choice = 0;
size_t unwrapper_idx_to = unwrap_indexes(df_iters, diff_len, ndiffs);
if (unwrapper_idx_to > 0) {
diffcmppath[unwrapper_idx_to].df_lev_score = -1;
}
try_possible_paths(df_iters, paths, npaths, 0, &choice, diffcmppath,
diff_len, ndiffs, diff_blk, iwhite);
diff_len, ndiffs, diff_blk, charmatch, word_offset,
word_offset_size);
return;
}
for (int i = 0; i <= diff_len[ch_dim]; i++) {
df_iters[ch_dim] = i;
populate_tensor(df_iters, ch_dim + 1, diffcmppath, diff_len,
ndiffs, diff_blk, iwhite);
ndiffs, diff_blk, charmatch, word_offset, word_offset_size);
}
}
@ -328,7 +346,8 @@ static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *d
/// @param [out] [allocated] decisions
/// @return the length of decisions
size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size_t ndiffs,
int **decisions, bool iwhite)
int **decisions, bool charmatch, size_t **word_offset,
size_t **word_offset_size)
{
assert(ndiffs <= LN_MAX_BUFS);
@ -353,7 +372,8 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
// memory for avoiding repetitive calculations of score
int df_iters[LN_MAX_BUFS];
populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, charmatch,
word_offset, word_offset_size);
const size_t u = unwrap_indexes(diff_len, diff_len, ndiffs);
diffcmppath_T *startNode = &diffcmppath[u];
@ -402,3 +422,16 @@ static size_t test_charmatch_paths(diffcmppath_T *node, int lastdecision)
}
return (size_t)node->df_choice_mem[lastdecision];
}
// return true if these two strings are equal
static bool compare(const char *s1, size_t l1, const char *s2, size_t l2)
{
if (l1 != l2) {
return false;
}
for (size_t i = 0; i < l1; i++) {
if (s1[i] != s2[i]) {
return false;
}
}
return true;
}

View File

@ -67,14 +67,29 @@ static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb,
int count_a, int start_b, int count_b, bool iwhite)
{
// get the pointer to char of the start of the diff to pass it to linematch algorithm
const char *diff_begin[2] = { ma->ptr, mb->ptr };
char *diff_begin[2] = { ma->ptr, mb->ptr };
int diff_length[2] = { count_a, count_b };
fastforward_buf_to_lnum(&diff_begin[0], (linenr_T)start_a + 1);
fastforward_buf_to_lnum(&diff_begin[1], (linenr_T)start_b + 1);
fastforward_buf_to_lnum((const char **)(&diff_begin[0]), (linenr_T)start_a + 1);
fastforward_buf_to_lnum((const char **)(&diff_begin[1]), (linenr_T)start_b + 1);
if (iwhite) {
for (int i = 0; i < 2; i++) {
size_t j = 0, k = 0, lines = (size_t)diff_length[i];
while (lines > 0) {
if (diff_begin[i][j] != ' ' && diff_begin[i][j] != '\t') {
diff_begin[i][k++] = diff_begin[i][j];
}
if (diff_begin[i][j++] == '\n') {
lines--;
}
}
}
}
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite);
size_t decisions_length = linematch_nbuffers((const char **)diff_begin, diff_length, 2,
&decisions, 0, NULL, NULL);
int lnuma = start_a;
int lnumb = start_b;

View File

@ -43,7 +43,7 @@
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/event/stream.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
@ -174,7 +174,7 @@ bool event_teardown(void)
loop_poll_events(&main_loop, 0); // Drain thread_events, fast_events.
input_stop();
channel_teardown();
process_teardown(&main_loop);
proc_teardown(&main_loop);
timer_teardown();
server_teardown();
signal_teardown();
@ -2207,7 +2207,7 @@ static void usage(void)
printf(_(" --headless Don't start a user interface\n"));
printf(_(" --listen <address> Serve RPC API from this address\n"));
printf(_(" --remote[-subcommand] Execute commands remotely on a server\n"));
printf(_(" --server <address> Specify RPC server to send commands to\n"));
printf(_(" --server <address> Connect to this Nvim server\n"));
printf(_(" --startuptime <file> Write startup timing messages to <file>\n"));
printf(_("\nSee \":help startup-options\" for all options.\n"));
}

View File

@ -84,7 +84,7 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/process.h"
#include "nvim/os/proc.h"
#include "nvim/os/time.h"
#include "nvim/os/time_defs.h"
#include "nvim/path.h"
@ -743,7 +743,7 @@ static void add_b0_fenc(ZeroBlock *b0p, buf_T *buf)
/// @param swap_fname Name of the swapfile. If it's from before a reboot, the result is 0.
///
/// @return PID, or 0 if process is not running or the swapfile is from before a reboot.
static int swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname)
static int swapfile_proc_running(const ZeroBlock *b0p, const char *swap_fname)
{
FileInfo st;
double uptime;
@ -1214,7 +1214,7 @@ void ml_recover(bool checkext)
msg(_("Recovery completed. Buffer contents equals file contents."), 0);
}
msg_puts(_("\nYou may want to delete the .swp file now."));
if (swapfile_process_running(b0p, fname_used)) {
if (swapfile_proc_running(b0p, fname_used)) {
// Warn there could be an active Vim on the same file, the user may
// want to kill it.
msg_puts(_("\nNote: process STILL RUNNING: "));
@ -1462,7 +1462,7 @@ char *make_percent_swname(char *dir, char *dir_end, const char *name)
}
// PID of swapfile owner, or zero if not running.
static int process_running;
static int proc_running;
/// For Vimscript "swapinfo()".
///
@ -1488,7 +1488,7 @@ void swapfile_dict(const char *fname, dict_T *d)
tv_dict_add_str_len(d, S_LEN("fname"), b0.b0_fname,
B0_FNAME_SIZE_ORG);
tv_dict_add_nr(d, S_LEN("pid"), swapfile_process_running(&b0, fname));
tv_dict_add_nr(d, S_LEN("pid"), swapfile_proc_running(&b0, fname));
tv_dict_add_nr(d, S_LEN("mtime"), char_to_long(b0.b0_mtime));
tv_dict_add_nr(d, S_LEN("dirty"), b0.b0_dirty ? 1 : 0);
tv_dict_add_nr(d, S_LEN("inode"), char_to_long(b0.b0_ino));
@ -1572,7 +1572,7 @@ static time_t swapfile_info(char *fname)
if (char_to_long(b0.b0_pid) != 0) {
msg_puts(_("\n process ID: "));
msg_outnum((int)char_to_long(b0.b0_pid));
if ((process_running = swapfile_process_running(&b0, fname))) {
if ((proc_running = swapfile_proc_running(&b0, fname))) {
msg_puts(_(" (STILL RUNNING)"));
}
}
@ -1640,7 +1640,7 @@ static bool swapfile_unchanged(char *fname)
}
// process must be known and not running.
if (char_to_long(b0.b0_pid) == 0 || swapfile_process_running(&b0, fname)) {
if (char_to_long(b0.b0_pid) == 0 || swapfile_proc_running(&b0, fname)) {
ret = false;
}
@ -3399,7 +3399,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
process_running = swapfile_process_running(&b0, fname);
proc_running = swapfile_proc_running(&b0, fname);
// If the swapfile has the same directory as the
// buffer don't compare the directory names, they can
@ -3459,7 +3459,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
choice = SEA_CHOICE_READONLY;
}
process_running = 0; // Set by attention_message..swapfile_info.
proc_running = 0; // Set by attention_message..swapfile_info.
if (choice == SEA_CHOICE_NONE) {
// Show info about the existing swapfile.
attention_message(buf, fname);
@ -3491,12 +3491,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
= do_dialog(VIM_WARNING,
_("VIM - ATTENTION"),
name,
process_running
proc_running
? _("&Open Read-Only\n&Edit anyway\n&Recover\n&Quit\n&Abort")
: _("&Open Read-Only\n&Edit anyway\n&Recover\n&Delete it\n&Quit\n&Abort"),
1, NULL, false);
if (process_running && dialog_result >= 4) {
if (proc_running && dialog_result >= 4) {
// compensate for missing "Delete it" button
dialog_result++;
}

View File

@ -14,7 +14,7 @@
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/wstream.h"
#include "nvim/globals.h"

View File

@ -344,7 +344,7 @@ char *os_getenvname_at_index(size_t index)
#endif
}
/// Get the process ID of the Neovim process.
/// Get the process ID of the Nvim process.
///
/// @return the process ID.
int64_t os_get_pid(void)

View File

@ -37,24 +37,24 @@
#include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/os/process.h"
#include "nvim/os/proc.h"
#ifdef MSWIN
# include "nvim/api/private/helpers.h"
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/process.c.generated.h"
# include "os/proc.c.generated.h"
#endif
#ifdef MSWIN
static bool os_proc_tree_kill_rec(HANDLE process, int sig)
static bool os_proc_tree_kill_rec(HANDLE proc, int sig)
{
if (process == NULL) {
if (proc == NULL) {
return false;
}
PROCESSENTRY32 pe;
DWORD pid = GetProcessId(process);
DWORD pid = GetProcessId(proc);
if (pid != 0) {
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
@ -77,7 +77,7 @@ static bool os_proc_tree_kill_rec(HANDLE process, int sig)
}
theend:
return (bool)TerminateProcess(process, (unsigned)sig);
return (bool)TerminateProcess(proc, (unsigned)sig);
}
/// Kills process `pid` and its descendants recursively.
bool os_proc_tree_kill(int pid, int sig)

View File

@ -7,5 +7,5 @@
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/process.h.generated.h"
# include "os/proc.h.generated.h"
#endif

View File

@ -143,7 +143,7 @@ finished:
return conpty_object;
}
bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *name,
bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *proc_handle, wchar_t *name,
wchar_t *cmd_line, wchar_t *cwd, wchar_t *env)
{
PROCESS_INFORMATION pi = { 0 };
@ -159,7 +159,7 @@ bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *n
&pi)) {
return false;
}
*process_handle = pi.hProcess;
*proc_handle = pi.hProcess;
return true;
}

7
src/nvim/os/pty_proc.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#ifdef MSWIN
# include "nvim/os/pty_proc_win.h"
#else
# include "nvim/os/pty_proc_unix.h"
#endif

View File

@ -34,16 +34,16 @@
#include "nvim/eval/typval.h"
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/log.h"
#include "nvim/os/fs.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/pty_process_unix.h"
#include "nvim/os/pty_proc.h"
#include "nvim/os/pty_proc_unix.h"
#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_unix.c.generated.h"
# include "os/pty_proc_unix.c.generated.h"
#endif
#if defined(__sun) && !defined(HAVE_FORKPTY)
@ -158,7 +158,7 @@ static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct win
#endif
/// @returns zero on success, or negative error code
int pty_process_spawn(PtyProcess *ptyproc)
int pty_proc_spawn(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
// termios initialized at first use
@ -168,7 +168,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
}
int status = 0; // zero or negative error code (libuv convention)
Process *proc = (Process *)ptyproc;
Proc *proc = (Proc *)ptyproc;
assert(proc->err.s.closed);
uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
@ -224,29 +224,29 @@ error:
return status;
}
const char *pty_process_tty_name(PtyProcess *ptyproc)
const char *pty_proc_tty_name(PtyProc *ptyproc)
{
return ptsname(ptyproc->tty_fd);
}
void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
FUNC_ATTR_NONNULL_ALL
{
ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
}
void pty_process_close(PtyProcess *ptyproc)
void pty_proc_close(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
pty_process_close_master(ptyproc);
Process *proc = (Process *)ptyproc;
pty_proc_close_master(ptyproc);
Proc *proc = (Proc *)ptyproc;
if (proc->internal_close_cb) {
proc->internal_close_cb(proc);
}
}
void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL
{
if (ptyproc->tty_fd >= 0) {
close(ptyproc->tty_fd);
@ -254,12 +254,12 @@ void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
}
}
void pty_process_teardown(Loop *loop)
void pty_proc_teardown(Loop *loop)
{
uv_signal_stop(&loop->children_watcher);
}
static void init_child(PtyProcess *ptyproc)
static void init_child(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
#if defined(HAVE__NSGETENVIRON)
@ -277,13 +277,13 @@ static void init_child(PtyProcess *ptyproc)
signal(SIGTERM, SIG_DFL);
signal(SIGALRM, SIG_DFL);
Process *proc = (Process *)ptyproc;
Proc *proc = (Proc *)ptyproc;
if (proc->cwd && os_chdir(proc->cwd) != 0) {
ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
return;
}
const char *prog = process_get_exepath(proc);
const char *prog = proc_get_exepath(proc);
assert(proc->env);
environ = tv_dict_to_env(proc->env);
@ -388,7 +388,7 @@ static void chld_handler(uv_signal_t *handle, int signum)
Loop *loop = handle->loop->data;
kl_iter(WatcherPtr, loop->children, current) {
Process *proc = (*current)->data;
Proc *proc = (*current)->data;
do {
pid = waitpid(proc->pid, &stat, WNOHANG);
} while (pid < 0 && errno == EINTR);
@ -406,10 +406,10 @@ static void chld_handler(uv_signal_t *handle, int signum)
}
}
PtyProcess pty_process_init(Loop *loop, void *data)
PtyProc pty_proc_init(Loop *loop, void *data)
{
PtyProcess rv;
rv.process = process_init(loop, kProcessTypePty, data);
PtyProc rv;
rv.proc = proc_init(loop, kProcTypePty, data);
rv.width = 80;
rv.height = 24;
rv.tty_fd = -1;

View File

@ -1,5 +1,5 @@
#pragma once
// IWYU pragma: private, include "nvim/os/pty_process.h"
// IWYU pragma: private, include "nvim/os/pty_proc.h"
#include <stdint.h>
#include <sys/ioctl.h>
@ -7,12 +7,12 @@
#include "nvim/event/defs.h"
typedef struct {
Process process;
Proc proc;
uint16_t width, height;
struct winsize winsize;
int tty_fd;
} PtyProcess;
} PtyProc;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_unix.h.generated.h"
# include "os/pty_proc_unix.h.generated.h"
#endif

View File

@ -10,20 +10,20 @@
#include "nvim/memory.h"
#include "nvim/os/os.h"
#include "nvim/os/pty_conpty_win.h"
#include "nvim/os/pty_process_win.h"
#include "nvim/os/pty_proc_win.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_win.c.generated.h"
# include "os/pty_proc_win.c.generated.h"
#endif
static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
static void CALLBACK pty_proc_finish1(void *context, BOOLEAN unused)
FUNC_ATTR_NONNULL_ALL
{
PtyProcess *ptyproc = (PtyProcess *)context;
Process *proc = (Process *)ptyproc;
PtyProc *ptyproc = (PtyProc *)context;
Proc *proc = (Proc *)ptyproc;
os_conpty_free(ptyproc->conpty);
// NB: pty_process_finish1() is called on a separate thread,
// NB: pty_proc_finish1() is called on a separate thread,
// but the timer only works properly if it's started by the main thread.
loop_schedule_fast(proc->loop, event_create(start_wait_eof_timer, ptyproc));
}
@ -31,7 +31,7 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
static void start_wait_eof_timer(void **argv)
FUNC_ATTR_NONNULL_ALL
{
PtyProcess *ptyproc = (PtyProcess *)argv[0];
PtyProc *ptyproc = (PtyProc *)argv[0];
if (ptyproc->finish_wait != NULL) {
uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200);
@ -39,15 +39,15 @@ static void start_wait_eof_timer(void **argv)
}
/// @returns zero on success, or negative error code.
int pty_process_spawn(PtyProcess *ptyproc)
int pty_proc_spawn(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
Process *proc = (Process *)ptyproc;
Proc *proc = (Proc *)ptyproc;
int status = 0;
conpty_t *conpty_object = NULL;
char *in_name = NULL;
char *out_name = NULL;
HANDLE process_handle = NULL;
HANDLE proc_handle = NULL;
uv_connect_t *in_req = NULL;
uv_connect_t *out_req = NULL;
wchar_t *cmd_line = NULL;
@ -69,7 +69,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
uv_pipe_connect(in_req,
&proc->in.uv.pipe,
in_name,
pty_process_connect_cb);
pty_proc_connect_cb);
}
if (!proc->out.s.closed) {
@ -77,7 +77,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
uv_pipe_connect(out_req,
&proc->out.s.uv.pipe,
out_name,
pty_process_connect_cb);
pty_proc_connect_cb);
}
if (proc->cwd != NULL) {
@ -105,7 +105,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
}
if (!os_conpty_spawn(conpty_object,
&process_handle,
&proc_handle,
NULL,
cmd_line,
cwd,
@ -114,42 +114,42 @@ int pty_process_spawn(PtyProcess *ptyproc)
status = (int)GetLastError();
goto cleanup;
}
proc->pid = (int)GetProcessId(process_handle);
proc->pid = (int)GetProcessId(proc_handle);
uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer);
ptyproc->wait_eof_timer.data = (void *)ptyproc;
if (!RegisterWaitForSingleObject(&ptyproc->finish_wait,
process_handle,
pty_process_finish1,
proc_handle,
pty_proc_finish1,
ptyproc,
INFINITE,
WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) {
abort();
}
// Wait until pty_process_connect_cb is called.
// Wait until pty_proc_connect_cb is called.
while ((in_req != NULL && in_req->handle != NULL)
|| (out_req != NULL && out_req->handle != NULL)) {
uv_run(&proc->loop->uv, UV_RUN_ONCE);
}
ptyproc->conpty = conpty_object;
ptyproc->process_handle = process_handle;
ptyproc->proc_handle = proc_handle;
conpty_object = NULL;
process_handle = NULL;
proc_handle = NULL;
cleanup:
if (status) {
// In the case of an error of MultiByteToWideChar or CreateProcessW.
ELOG("pty_process_spawn(%s): %s: error code: %d",
ELOG("pty_proc_spawn(%s): %s: error code: %d",
proc->argv[0], emsg, status);
status = os_translate_sys_error(status);
}
os_conpty_free(conpty_object);
xfree(in_name);
xfree(out_name);
if (process_handle != NULL) {
CloseHandle(process_handle);
if (proc_handle != NULL) {
CloseHandle(proc_handle);
}
xfree(in_req);
xfree(out_req);
@ -159,32 +159,32 @@ cleanup:
return status;
}
const char *pty_process_tty_name(PtyProcess *ptyproc)
const char *pty_proc_tty_name(PtyProc *ptyproc)
{
return "?";
}
void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
FUNC_ATTR_NONNULL_ALL
{
os_conpty_set_size(ptyproc->conpty, width, height);
}
void pty_process_close(PtyProcess *ptyproc)
void pty_proc_close(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
Process *proc = (Process *)ptyproc;
Proc *proc = (Proc *)ptyproc;
pty_process_close_master(ptyproc);
pty_proc_close_master(ptyproc);
if (ptyproc->finish_wait != NULL) {
UnregisterWaitEx(ptyproc->finish_wait, NULL);
ptyproc->finish_wait = NULL;
uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL);
}
if (ptyproc->process_handle != NULL) {
CloseHandle(ptyproc->process_handle);
ptyproc->process_handle = NULL;
if (ptyproc->proc_handle != NULL) {
CloseHandle(ptyproc->proc_handle);
ptyproc->proc_handle = NULL;
}
if (proc->internal_close_cb) {
@ -192,17 +192,17 @@ void pty_process_close(PtyProcess *ptyproc)
}
}
void pty_process_close_master(PtyProcess *ptyproc)
void pty_proc_close_master(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
}
void pty_process_teardown(Loop *loop)
void pty_proc_teardown(Loop *loop)
FUNC_ATTR_NONNULL_ALL
{
}
static void pty_process_connect_cb(uv_connect_t *req, int status)
static void pty_proc_connect_cb(uv_connect_t *req, int status)
FUNC_ATTR_NONNULL_ALL
{
assert(status == 0);
@ -212,23 +212,23 @@ static void pty_process_connect_cb(uv_connect_t *req, int status)
static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer)
FUNC_ATTR_NONNULL_ALL
{
PtyProcess *ptyproc = wait_eof_timer->data;
Process *proc = (Process *)ptyproc;
PtyProc *ptyproc = wait_eof_timer->data;
Proc *proc = (Proc *)ptyproc;
assert(ptyproc->finish_wait != NULL);
if (proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream)) {
uv_timer_stop(&ptyproc->wait_eof_timer);
pty_process_finish2(ptyproc);
pty_proc_finish2(ptyproc);
}
}
static void pty_process_finish2(PtyProcess *ptyproc)
static void pty_proc_finish2(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
Process *proc = (Process *)ptyproc;
Proc *proc = (Proc *)ptyproc;
DWORD exit_code = 0;
GetExitCodeProcess(ptyproc->process_handle, &exit_code);
GetExitCodeProcess(ptyproc->proc_handle, &exit_code);
proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code;
proc->internal_exit_cb(proc);
@ -427,14 +427,14 @@ cleanup:
return rc;
}
PtyProcess pty_process_init(Loop *loop, void *data)
PtyProc pty_proc_init(Loop *loop, void *data)
{
PtyProcess rv;
rv.process = process_init(loop, kProcessTypePty, data);
PtyProc rv;
rv.proc = proc_init(loop, kProcTypePty, data);
rv.width = 80;
rv.height = 24;
rv.conpty = NULL;
rv.finish_wait = NULL;
rv.process_handle = NULL;
rv.proc_handle = NULL;
return rv;
}

View File

@ -1,20 +1,20 @@
#pragma once
// IWYU pragma: private, include "nvim/os/pty_process.h"
// IWYU pragma: private, include "nvim/os/pty_proc.h"
#include <uv.h>
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/lib/queue_defs.h"
#include "nvim/os/pty_conpty_win.h"
typedef struct pty_process {
Process process;
Proc proc;
uint16_t width, height;
conpty_t *conpty;
HANDLE finish_wait;
HANDLE process_handle;
HANDLE proc_handle;
uv_timer_t wait_eof_timer;
} PtyProcess;
} PtyProc;
// Structure used by build_cmd_line()
typedef struct arg_node {
@ -23,5 +23,5 @@ typedef struct arg_node {
} ArgNode;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_win.h.generated.h"
# include "os/pty_proc_win.h.generated.h"
#endif

View File

@ -1,7 +0,0 @@
#pragma once
#ifdef MSWIN
# include "nvim/os/pty_process_win.h"
#else
# include "nvim/os/pty_process_unix.h"
#endif

View File

@ -14,10 +14,10 @@
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/libuv_proc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
@ -872,12 +872,12 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
char prog[MAXPATHL];
xstrlcpy(prog, argv[0], MAXPATHL);
LibuvProcess uvproc = libuv_process_init(&main_loop, &buf);
Process *proc = &uvproc.process;
LibuvProc uvproc = libuv_proc_init(&main_loop, &buf);
Proc *proc = &uvproc.proc;
MultiQueue *events = multiqueue_new_child(main_loop.events);
proc->events = events;
proc->argv = argv;
int status = process_spawn(proc, has_input, true, true);
int status = proc_spawn(proc, has_input, true, true);
if (status) {
loop_poll_events(&main_loop, 0);
// Failed, probably 'shell' is not executable.
@ -910,7 +910,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
if (!wstream_write(&proc->in, input_buffer)) {
// couldn't write, stop the process and tell the user about it
process_stop(proc);
proc_stop(proc);
return -1;
}
// close the input stream after everything is written
@ -927,7 +927,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
msg_no_more = true;
lines_left = -1;
}
int exitcode = process_wait(proc, -1, NULL);
int exitcode = proc_wait(proc, -1, NULL);
if (!got_int && out_data_decide_throttle(0)) {
// Last chunk of output was skipped; display it now.
out_data_ring(NULL, SIZE_MAX);

View File

@ -950,8 +950,8 @@ void time_msg(const char *mesg, const proftime_T *start)
/// Initializes the `time_fd` stream for the --startuptime report.
///
/// @param fname startuptime report file path
/// @param process_name name of the current Nvim process to write in the report.
void time_init(const char *fname, const char *process_name)
/// @param proc_name name of the current Nvim process to write in the report.
void time_init(const char *fname, const char *proc_name)
{
const size_t bufsize = 8192; // Big enough for the entire --startuptime report.
time_fd = fopen(fname, "a");
@ -972,7 +972,7 @@ void time_init(const char *fname, const char *process_name)
semsg("time_init: setvbuf failed: %d %s", r, uv_err_name(r));
return;
}
fprintf(time_fd, "--- Startup times for process: %s ---\n", process_name);
fprintf(time_fd, "--- Startup times for process: %s ---\n", proc_name);
}
/// Flushes the startuptimes to disk for the current process

View File

@ -6875,7 +6875,8 @@ bool set_ref_in_quickfix(int copyID)
// In a location list window and none of the other windows is
// referring to this location list. Mark the location list
// context as still in use.
if (mark_quickfix_ctx(win->w_llist_ref, copyID)) {
if (mark_quickfix_ctx(win->w_llist_ref, copyID)
|| mark_quickfix_user_data(win->w_llist_ref, copyID)) {
return true;
}
}

View File

@ -103,7 +103,7 @@ Debugging tests
DBG 2022-06-15T18:37:45.227 T57.58016.0/c UI: stop
INF 2022-06-15T18:37:45.227 T57.58016.0/c os_exit:595: Nvim exit: 0
DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
INF 2022-06-15T18:37:45.229 T57.58016.0 on_process_exit:400: exited: pid=58017 status=0 stoptime=0
INF 2022-06-15T18:37:45.229 T57.58016.0 on_proc_exit:400: exited: pid=58017 status=0 stoptime=0
```
- You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527):

View File

@ -114,6 +114,7 @@ function Session:request(method, ...)
return true, result
end
--- Runs the event loop.
function Session:run(request_cb, notification_cb, setup_cb, timeout)
local function on_request(method, args, response)
coroutine_exec(request_cb, method, args, function(status, result, flag)

View File

@ -1,6 +1,6 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local clear = n.clear
local feed_command = n.feed_command

View File

@ -1,6 +1,6 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local uv = vim.uv
local clear, command, testprg = n.clear, n.command, n.testprg
@ -199,7 +199,7 @@ end)
describe('autocmd TextChangedT', function()
clear()
local screen = tt.screen_setup()
local screen = tt.setup_screen()
it('works', function()
command('autocmd TextChangedT * ++once let g:called = 1')

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local clear = n.clear
local eq = t.eq

View File

@ -1,6 +1,6 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local assert_log = t.assert_log
local clear = n.clear

View File

@ -157,5 +157,28 @@ describe('vim.ui', function()
exec_lua [[local _, err = vim.ui.open('foo') ; return err]]
)
end)
it('opt.cmd #29490', function()
t.matches(
'ENOENT: no such file or directory',
t.pcall_err(exec_lua, function()
vim.ui.open('foo', { cmd = { 'non-existent-tool' } })
end)
)
eq(
{
code = 0,
signal = 0,
stderr = '',
stdout = 'arg1=arg1;arg2=https://example.com;',
},
exec_lua(function(cmd_)
local cmd, err = vim.ui.open('https://example.com', { cmd = cmd_ })
assert(cmd and not err)
return cmd:wait()
end, { n.testprg('printargs-test'), 'arg1' })
)
end)
end)
end)

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local clear, eq, api = n.clear, t.eq, n.api
local feed = n.feed
local feed_data = tt.feed_data
@ -17,7 +17,7 @@ describe(':terminal altscreen', function()
before_each(function()
clear()
screen = tt.screen_setup()
screen = tt.setup_screen()
feed_data({
'line1',
'line2',

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local ok = t.ok
if t.skip(t.is_os('win')) then

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local assert_alive = n.assert_alive
local feed, clear = n.feed, n.clear
@ -29,7 +29,7 @@ describe(':terminal buffer', function()
before_each(function()
clear()
command('set modifiable swapfile undolevels=20')
screen = tt.screen_setup()
screen = tt.setup_screen()
end)
it('terminal-mode forces various options', function()
@ -574,7 +574,7 @@ if is_os('win') then
feed_command('set modifiable swapfile undolevels=20')
poke_eventloop()
local cmd = { 'cmd.exe', '/K', 'PROMPT=$g$s' }
screen = tt.screen_setup(nil, cmd)
screen = tt.setup_screen(nil, cmd)
end)
it('"put" operator sends data normally', function()

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local feed, clear = n.feed, n.clear
local testprg, command = n.testprg, n.command
@ -18,7 +18,7 @@ describe(':terminal cursor', function()
before_each(function()
clear()
screen = tt.screen_setup()
screen = tt.setup_screen()
end)
it('moves the screen cursor when focused', function()

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local feed, clear = n.feed, n.clear
local api = n.api

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local clear, eq, eval = n.clear, t.eq, n.eval
local feed, api, command = n.feed, n.api, n.command
local feed_data = tt.feed_data
@ -14,7 +14,7 @@ describe(':terminal mouse', function()
before_each(function()
clear()
api.nvim_set_option_value('statusline', '==========', {})
screen = tt.screen_setup()
screen = tt.setup_screen()
command('highlight StatusLine NONE')
command('highlight StatusLineNC NONE')
command('highlight StatusLineTerm NONE')

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local clear, eq = n.clear, t.eq
local feed, testprg = n.feed, n.testprg
@ -22,7 +22,7 @@ describe(':terminal scrollback', function()
before_each(function()
clear()
screen = tt.screen_setup(nil, nil, 30)
screen = tt.setup_screen(nil, nil, 30)
end)
describe('when the limit is exceeded', function()
@ -399,9 +399,9 @@ describe("'scrollback' option", function()
it('set to 0 behaves as 1', function()
local screen
if is_os('win') then
screen = tt.screen_setup(nil, { 'cmd.exe' }, 30)
screen = tt.setup_screen(nil, { 'cmd.exe' }, 30)
else
screen = tt.screen_setup(nil, { 'sh' }, 30)
screen = tt.setup_screen(nil, { 'sh' }, 30)
end
api.nvim_set_option_value('scrollback', 0, {})
@ -416,10 +416,10 @@ describe("'scrollback' option", function()
local screen
if is_os('win') then
command([[let $PROMPT='$$']])
screen = tt.screen_setup(nil, { 'cmd.exe' }, 30)
screen = tt.setup_screen(nil, { 'cmd.exe' }, 30)
else
command('let $PS1 = "$"')
screen = tt.screen_setup(nil, { 'sh' }, 30)
screen = tt.setup_screen(nil, { 'sh' }, 30)
end
api.nvim_set_option_value('scrollback', 200, {})
@ -480,8 +480,8 @@ describe("'scrollback' option", function()
end)
it('deletes extra lines immediately', function()
-- Scrollback is 10 on screen_setup
local screen = tt.screen_setup(nil, nil, 30)
-- Scrollback is 10 on setup_screen
local screen = tt.setup_screen(nil, nil, 30)
local lines = {}
for i = 1, 30 do
table.insert(lines, 'line' .. tostring(i))

View File

@ -7,7 +7,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local eq = t.eq
local feed_data = tt.feed_data
@ -2111,7 +2111,7 @@ describe('TUI', function()
finally(function()
os.remove('testF')
end)
local screen = tt.screen_setup(
local screen = tt.setup_screen(
0,
('"%s" -u NONE -i NONE --cmd "set noswapfile noshowcmd noruler" --cmd "normal iabc" > /dev/null 2>&1 && cat testF && rm testF'):format(
nvim_prog

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local feed_data = tt.feed_data
local feed, clear = n.feed, n.clear
local poke_eventloop = n.poke_eventloop
@ -37,7 +37,7 @@ describe(':terminal window', function()
before_each(function()
clear()
screen = tt.screen_setup()
screen = tt.setup_screen()
end)
it('sets topline correctly #8556', function()
@ -198,7 +198,7 @@ describe(':terminal with multigrid', function()
before_each(function()
clear()
screen = tt.screen_setup(0, nil, 50, nil, { ext_multigrid = true })
screen = tt.setup_screen(0, nil, 50, nil, { ext_multigrid = true })
end)
it('resizes to requested size', function()

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local assert_alive = n.assert_alive
local clear = n.clear
local feed = n.feed
@ -22,7 +22,7 @@ describe(':terminal', function()
-- set the statusline to a constant value because of variables like pid
-- and current directory and to improve visibility of splits
api.nvim_set_option_value('statusline', '==========', {})
screen = tt.screen_setup(3)
screen = tt.setup_screen(3)
command('highlight StatusLine NONE')
command('highlight StatusLineNC NONE')
command('highlight StatusLineTerm NONE')

View File

@ -250,6 +250,8 @@ function M.set_method_error(err)
method_error = err
end
--- Runs the event loop of the given session.
---
--- @param lsession test.Session
--- @param request_cb function?
--- @param notification_cb function?
@ -296,6 +298,7 @@ function M.run_session(lsession, request_cb, notification_cb, setup_cb, timeout)
return lsession.eof_err
end
--- Runs the event loop of the current global session.
function M.run(request_cb, notification_cb, setup_cb, timeout)
assert(session)
return M.run_session(session, request_cb, notification_cb, setup_cb, timeout)

View File

@ -1,6 +1,13 @@
-- To test tui/input.c, this module spawns `nvim` inside :terminal and sends
-- bytes via jobsend(). Note: the functional/testutil.lua test-session methods
-- operate on the _host_ session, _not_ the child session.
-- Functions to test :terminal and the Nvim TUI.
-- Starts a child process in a `:terminal` and sends bytes to the child via nvim_chan_send().
-- Note: the global functional/testutil.lua test-session is _host_ session, _not_
-- the child session.
--
-- - Use `setup_screen()` to test `:terminal` behavior with an arbitrary command.
-- - Use `setup_child_nvim()` to test the Nvim TUI.
-- - NOTE: Only use this if your test actually needs the full lifecycle/capabilities of the
-- builtin Nvim TUI. Most tests should just use `Screen.new()` directly, or plain old API calls.
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
@ -9,18 +16,20 @@ local exec_lua = n.exec_lua
local api = n.api
local nvim_prog = n.nvim_prog
local function feed_data(data)
local M = {}
function M.feed_data(data)
if type(data) == 'table' then
data = table.concat(data, '\n')
end
exec_lua('vim.api.nvim_chan_send(vim.b.terminal_job_id, ...)', data)
end
local function feed_termcode(data)
feed_data('\027' .. data)
function M.feed_termcode(data)
M.feed_data('\027' .. data)
end
local function make_lua_executor(session)
function M.make_lua_executor(session)
return function(code, ...)
local status, rv = session:request('nvim_exec_lua', code, { ... })
if not status then
@ -34,60 +43,68 @@ end
-- some t for controlling the terminal. the codes were taken from
-- infocmp xterm-256color which is less what libvterm understands
-- civis/cnorm
local function hide_cursor()
feed_termcode('[?25l')
function M.hide_cursor()
M.feed_termcode('[?25l')
end
local function show_cursor()
feed_termcode('[?25h')
function M.show_cursor()
M.feed_termcode('[?25h')
end
-- smcup/rmcup
local function enter_altscreen()
feed_termcode('[?1049h')
function M.enter_altscreen()
M.feed_termcode('[?1049h')
end
local function exit_altscreen()
feed_termcode('[?1049l')
function M.exit_altscreen()
M.feed_termcode('[?1049l')
end
-- character attributes
local function set_fg(num)
feed_termcode('[38;5;' .. num .. 'm')
function M.set_fg(num)
M.feed_termcode('[38;5;' .. num .. 'm')
end
local function set_bg(num)
feed_termcode('[48;5;' .. num .. 'm')
function M.set_bg(num)
M.feed_termcode('[48;5;' .. num .. 'm')
end
local function set_bold()
feed_termcode('[1m')
function M.set_bold()
M.feed_termcode('[1m')
end
local function set_italic()
feed_termcode('[3m')
function M.set_italic()
M.feed_termcode('[3m')
end
local function set_underline()
feed_termcode('[4m')
function M.set_underline()
M.feed_termcode('[4m')
end
local function set_underdouble()
feed_termcode('[4:2m')
function M.set_underdouble()
M.feed_termcode('[4:2m')
end
local function set_undercurl()
feed_termcode('[4:3m')
function M.set_undercurl()
M.feed_termcode('[4:3m')
end
local function set_strikethrough()
feed_termcode('[9m')
function M.set_strikethrough()
M.feed_termcode('[9m')
end
local function clear_attrs()
feed_termcode('[0;10m')
function M.clear_attrs()
M.feed_termcode('[0;10m')
end
-- mouse
local function enable_mouse()
feed_termcode('[?1002h')
function M.enable_mouse()
M.feed_termcode('[?1002h')
end
local function disable_mouse()
feed_termcode('[?1002l')
function M.disable_mouse()
M.feed_termcode('[?1002l')
end
local default_command = { testprg('tty-test') }
local function screen_setup(extra_rows, command, cols, env, screen_opts)
--- Runs `cmd` in a :terminal, and returns a `Screen` object.
---
---@param extra_rows? integer Extra rows to add to the default screen.
---@param cmd? string|string[] Command to run in the terminal (default: `{ 'tty-test' }`)
---@param cols? integer Create screen with this many columns (default: 50)
---@param env? table Environment set on the `cmd` job.
---@param screen_opts? table Options for `Screen.new()`.
---@return test.functional.ui.screen # Screen attached to the global (not child) Nvim session.
function M.setup_screen(extra_rows, cmd, cols, env, screen_opts)
extra_rows = extra_rows and extra_rows or 0
command = command and command or default_command
cmd = cmd and cmd or default_command
cols = cols and cols or 50
api.nvim_command('highlight TermCursor cterm=reverse')
@ -120,7 +137,7 @@ local function screen_setup(extra_rows, command, cols, env, screen_opts)
screen:attach(screen_opts or { rgb = false })
api.nvim_command('enew')
api.nvim_call_function('termopen', { command, env and { env = env } or nil })
api.nvim_call_function('termopen', { cmd, env and { env = env } or nil })
api.nvim_input('<CR>')
local vim_errmsg = api.nvim_eval('v:errmsg')
if vim_errmsg and '' ~= vim_errmsg then
@ -133,7 +150,7 @@ local function screen_setup(extra_rows, command, cols, env, screen_opts)
-- tty-test puts the terminal into raw mode and echoes input. Tests work by
-- feeding termcodes to control the display and asserting by screen:expect.
if command == default_command and screen_opts == nil then
if cmd == default_command and screen_opts == nil then
-- Wait for "tty ready" to be printed before each test or the terminal may
-- still be in canonical mode (will echo characters for example).
local empty_line = (' '):rep(cols)
@ -160,37 +177,24 @@ local function screen_setup(extra_rows, command, cols, env, screen_opts)
return screen
end
local function setup_child_nvim(args, opts)
--- Spawns Nvim with `args` in a :terminal, and returns a `Screen` object.
---
--- @note Only use this if you actually need the full lifecycle/capabilities of the builtin Nvim
--- TUI. Most tests should just use `Screen.new()` directly, or plain old API calls.
---
---@param args? string[] Args passed to child Nvim.
---@param opts? table Options
---@return test.functional.ui.screen # Screen attached to the global (not child) Nvim session.
function M.setup_child_nvim(args, opts)
opts = opts or {}
local argv = { nvim_prog, unpack(args) }
local argv = { nvim_prog, unpack(args or {}) }
local env = opts.env or {}
if not env.VIMRUNTIME then
env.VIMRUNTIME = os.getenv('VIMRUNTIME')
end
return screen_setup(opts.extra_rows, argv, opts.cols, env)
return M.setup_screen(opts.extra_rows, argv, opts.cols, env)
end
return {
feed_data = feed_data,
feed_termcode = feed_termcode,
make_lua_executor = make_lua_executor,
hide_cursor = hide_cursor,
show_cursor = show_cursor,
enter_altscreen = enter_altscreen,
exit_altscreen = exit_altscreen,
set_fg = set_fg,
set_bg = set_bg,
set_bold = set_bold,
set_italic = set_italic,
set_underline = set_underline,
set_underdouble = set_underdouble,
set_undercurl = set_undercurl,
set_strikethrough = set_strikethrough,
clear_attrs = clear_attrs,
enable_mouse = enable_mouse,
disable_mouse = disable_mouse,
screen_setup = screen_setup,
setup_child_nvim = setup_child_nvim,
}
return M

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

@ -1078,6 +1078,44 @@ describe('CursorLine and CursorLineNr highlights', function()
]])
end)
-- oldtest: Test_cursorline_screenline_resize()
it("'cursorlineopt' screenline is updated on window resize", function()
local screen = Screen.new(75, 8)
screen:attach()
exec([[
50vnew
call setline(1, repeat('xyz ', 30))
setlocal number cursorline cursorlineopt=screenline
normal! $
]])
screen:expect([[
{8: 1 }xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xy |
{8: }z xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz {1:~ }|
{8: }{21:xyz xyz xyz xyz xyz xyz xyz^ }{1:~ }|
{1:~ }{1:~ }|*3
{3:[No Name] [+] }{2:[No Name] }|
|
]])
command('vertical resize -4')
screen:expect([[
{8: 1 }xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xy |
{8: }z xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz {1:~ }|
{8: }{21:xyz xyz xyz xyz xyz xyz xyz xyz xyz^ }{1:~ }|
{1:~ }{1:~ }|*3
{3:[No Name] [+] }{2:[No Name] }|
|
]])
command('set cpoptions+=n')
screen:expect([[
{8: 1 }xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xy |
z xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz {1:~ }|
{21:xyz xyz xyz xyz xyz xyz xyz xyz^ }{1:~ }|
{1:~ }{1:~ }|*3
{3:[No Name] [+] }{2:[No Name] }|
|
]])
end)
-- oldtest: Test_cursorline_after_yank()
it('always updated. vim-patch:8.1.0849', function()
local screen = Screen.new(50, 5)

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local clear, insert = n.clear, n.insert
local command = n.command

View File

@ -803,6 +803,73 @@ void testFunction () {
]])
end)
end)
describe('setup a diff with 2 files and set linematch:30', function()
before_each(function()
feed(':set diffopt+=linematch:30<cr>')
local f1 = [[
start
?a
]]
local f2 = [[
start
!b
!a
!c
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }^start {7: }{8: 1 }start |
{7: }{8: 2 }{22:!b }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:!a }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 }{4: }{27:!c}{4: }{7: }{8: 2 }{4: }{27:?a}{4: }|
{7: }{8: 5 } {7: }{8: 3 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:e |
]])
end)
it('display results with ignore white', function()
feed(':set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }^start {7: }{8: 1 }start |
{7: }{8: 2 }{22:!b }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{27:!}{4:a }{7: }{8: 2 }{4: }{27:?}{4:a }|
{7: }{8: 4 }{22: !c }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 5 } {7: }{8: 3 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
describe('a diff that would result in multiple groups before grouping optimization', function()
before_each(function()
feed(':set diffopt+=linematch:30<cr>')
@ -1084,6 +1151,436 @@ something
end
)
end)
describe('show a diff with charmatch enabled', function()
before_each(function()
local f1 = [[
abbcabbcdefghijklmnop
]]
local f2 = [[
abca?bc
dfgh?ijl
mnop?
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('when the entire hunk is compared, cross-line', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^abca}{27:?}{4:bc }{7: }{8: 1 }{4:a}{27:b}{4:bca}{27:b}{4:bcd}{27:e}{4:fghij}{27:k}{4:lmnop }|
{7: }{8: 2 }{4:dfgh}{27:?}{4:ijl }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{4:mnop}{27:?}{4: }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
describe('when the single line is compared, cross-line', function()
before_each(function()
feed(':set diffopt+=chardiff:30<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^abca}{27:?}{4:bc }{7: }{8: 1 }{4:a}{27:b}{4:bca}{27:b}{4:bc}{27:defghijklmnop}{4: }|
{7: }{8: 2 }{22:dfgh?ijl }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:mnop? }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:30 |
]])
end)
end)
describe('when the diff hunk and the single line are too long to run chardiff', function()
before_each(function()
feed(':set diffopt+=chardiff:10<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^ab}{27:ca?bc}{4: }{7: }{8: 1 }{4:ab}{27:bcabbcdefghijklmnop}{4: }|
{7: }{8: 2 }{22:dfgh?ijl }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:mnop? }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:10 |
]])
end)
end)
end)
describe('show a diff with wordmatch enabled', function()
before_each(function()
local f1 = [[
wA w1 wB w1 w2 wC w3 w4
]]
local f2 = [[
w1 w2
w2 w3
w4 w5
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('when the entire hunk is compared, cross-line', function()
it('display results', function()
feed(':set diffopt+=worddiff:30<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA w1 wB }{4:w1 w2}{27: wC}{4: w3}{27: }{4:w4 }|
{7: }{8: 2 }{27:w2}{4: w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{4:w4}{27: w5}{4: }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=worddiff:30 |
]])
end)
it('display results, with ignore white', function()
feed(':set diffopt+=worddiff:20<cr>:set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA}{4: }{27:w1}{4: }{27:wB}{4: w1 w2 }{27:wC}{4: w3 w4 }|
{7: }{8: 2 }{27:w2}{4: w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{4:w4 }{27:w5}{4: }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
describe('when the single line is compared, cross-line', function()
it('display results', function()
feed(':set diffopt+=worddiff:20<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA w1 wB }{4:w1 w2}{27: wC w3 w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=worddiff:20 |
]])
end)
it('display results, with ignore white', function()
feed(':set diffopt+=worddiff:15<cr>:set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA}{4: }{27:w1}{4: }{27:wB}{4: w1 w2 }{27:wC}{4: }{27:w3}{4: }{27:w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
describe('when the diff hunk and the single line are too long to run chardiff', function()
it('display results', function()
feed(':set diffopt+=worddiff:10<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w}{27:1 w2}{4: }{7: }{8: 1 }{4:w}{27:A w1 wB w1 w2 wC w3 w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=worddiff:10 |
]])
end)
it('display results, with ignore white', function()
feed(':set diffopt+=worddiff:10<cr>:set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w}{27:1 w2}{4: }{7: }{8: 1 }{4:w}{27:A w1 wB w1 w2 wC w3 w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
end)
describe('show a diff with charmatch enabled, with and without ignore white', function()
before_each(function()
local f1 = [[
ababcabcdabcde
]]
local f2 = [[
abc abcd abcde abcdef
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('normal comparison, including whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^ab}{27:c }{4:abc}{27:d }{4:abcd}{27:e }{4:abcde}{27:f}{4: }{7: }{8: 1 }{4:ababcabcdabcde }|
{7: }{8: 2 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
describe('ignore whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>:set diffopt+=iwhiteall<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^ab}{27:c}{4: abc}{27:d}{4: abcd}{27:e}{4: abcde}{27:f}{4: }{7: }{8: 1 }{4:ababcabcdabcde }|
{7: }{8: 2 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
end)
describe('show a diff with charmatch enabled, with different UTF-8 character', function()
before_each(function()
local f1 = [[
aaaहaaa
]]
local f2 = [[
aaaसaaa
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('normal comparison, including whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^aaa}{27:}{4:a}{27:a}{4:a }{7: }{8: 1 }{4:aaa}{27:}{4:a}{27:a}{4:a }|
{7: }{8: 2 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
end)
describe('show a diff with charmatch enabled, with same UTF-8 character', function()
before_each(function()
local f1 = [[
aaaहaaa
]]
local f2 = [[
aaaहaaa
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('normal comparison, including whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7:+ }{8: 1 }{13:^+-- 2 lines: aaaहaaa······················}{7:+ }{8: 1 }{13:+-- 2 lines: aaaहaaa·······················}|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
end)
end)
describe('regressions', function()

View File

@ -1,7 +1,7 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')
local tt = require('test.functional.testterm')
local assert_alive = n.assert_alive
local mkdir, write_file, rmdir = t.mkdir, t.write_file, n.rmdir

View File

@ -262,14 +262,34 @@ func Test_cursorline_callback()
call timer_start(300, 'Func')
END
call writefile(lines, 'Xcul_timer')
call writefile(lines, 'Xcul_timer', 'D')
let buf = RunVimInTerminal('-S Xcul_timer', #{rows: 8})
call TermWait(buf, 310)
call VerifyScreenDump(buf, 'Test_cursorline_callback_1', {})
call StopVimInTerminal(buf)
call delete('Xcul_timer')
endfunc
func Test_cursorline_screenline_resize()
CheckScreendump
let lines =<< trim END
50vnew
call setline(1, repeat('xyz ', 30))
setlocal number cursorline cursorlineopt=screenline
normal! $
END
call writefile(lines, 'Xcul_screenline_resize', 'D')
let buf = RunVimInTerminal('-S Xcul_screenline_resize', #{rows: 8})
call VerifyScreenDump(buf, 'Test_cursorline_screenline_resize_1', {})
call term_sendkeys(buf, ":vertical resize -4\<CR>")
call VerifyScreenDump(buf, 'Test_cursorline_screenline_resize_2', {})
call term_sendkeys(buf, ":set cpoptions+=n\<CR>")
call VerifyScreenDump(buf, 'Test_cursorline_screenline_resize_3', {})
call StopVimInTerminal(buf)
endfunc
func Test_cursorline_screenline_update()
@ -280,7 +300,7 @@ func Test_cursorline_screenline_update()
set cursorline cursorlineopt=screenline
inoremap <F2> <Cmd>call cursor(1, 1)<CR>
END
call writefile(lines, 'Xcul_screenline')
call writefile(lines, 'Xcul_screenline', 'D')
let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8})
call term_sendkeys(buf, "A")
@ -290,7 +310,17 @@ func Test_cursorline_screenline_update()
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
call delete('Xcul_screenline')
endfunc
func Test_cursorline_screenline_zero_width()
CheckOption foldcolumn
set cursorline culopt=screenline winminwidth=1 foldcolumn=1
" This used to crash Vim
1vnew | redraw
bwipe!
set cursorline& culopt& winminwidth& foldcolumn&
endfunc
func Test_cursorline_cursorbind_horizontal_scroll()

View File

@ -1150,15 +1150,14 @@ func Test_cfg_file()
unlet g:filetype_cfg
" RAPID cfg
let ext = 'cfg'
for i in ['EIO', 'MMC', 'MOC', 'PROC', 'SIO', 'SYS']
for ext in ['cfg', 'Cfg', 'CFG']
call writefile([i .. ':CFG'], 'cfgfile.' .. ext)
execute "split cfgfile." .. ext
call assert_equal('rapid', &filetype)
bwipe!
call delete('cfgfile.' .. ext)
" check different case of file extension
let ext = substitute(ext, '\(\l\)', '\u\1', '')
endfor
endfor
" clean up

View File

@ -4071,11 +4071,23 @@ func Test_ll_window_ctx()
enew | only
endfunc
" Similar to the problem above, but for user data.
func Test_ll_window_user_data()
call setloclist(0, [#{bufnr: bufnr(), user_data: {}}])
lopen
wincmd t
close
call test_garbagecollect_now()
call feedkeys("\<CR>", 'tx')
call test_garbagecollect_now()
%bwipe!
endfunc
" The following test used to crash vim
func Test_lfile_crash()
sp Xtest
au QuickFixCmdPre * bw
call assert_fails('lfile', 'E40')
call assert_fails('lfile', 'E40:')
au! QuickFixCmdPre
endfunc