Skip to content

Commit 4f089a4

Browse files
author
skywind3000
committed
stability improvement
1 parent 0013ffe commit 4f089a4

3 files changed

Lines changed: 913 additions & 16 deletions

File tree

autoload/quickui/window.vim

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ function! s:window.__prepare_opts(textlist, opts)
122122
endif
123123
endif
124124
let info.cmd = cmd
125-
let info.pending_cmd = []
125+
let info.pending_cmd = get(info, 'pending_cmd', [])
126126
let info.border_winid = -1
127127
let info.border_bid = -1
128128
endfunc
@@ -303,7 +303,7 @@ function! s:window.__nvim_show()
303303
call quickui#core#win_execute(winid, info.pending_cmd)
304304
let info.pending_cmd = []
305305
endif
306-
call nvim_win_set_option(self.winid, 'winhl', 'Normal:'. color)
306+
call nvim_win_set_option(self.winid, 'winhl', 'Normal:'. color)
307307
if info.has_border
308308
let bwid = nvim_open_win(info.border_bid, 0, info.border_opts)
309309
let info.border_winid = bwid
@@ -325,11 +325,17 @@ function! s:window.__nvim_hide()
325325
endif
326326
let info = self.info
327327
if info.border_winid >= 0
328-
call nvim_win_close(info.border_winid, 1)
328+
try
329+
call nvim_win_close(info.border_winid, 1)
330+
catch
331+
endtry
329332
let info.border_winid = -1
330333
endif
331334
if self.winid >= 0
332-
call nvim_win_close(self.winid, 1)
335+
try
336+
call nvim_win_close(self.winid, 1)
337+
catch
338+
endtry
333339
let self.winid = -1
334340
endif
335341
let self.hide = 1
@@ -358,11 +364,20 @@ endfunc
358364
function! s:window.close()
359365
if self.winid >= 0
360366
if s:has_nvim == 0
361-
call popup_close(self.winid)
367+
try
368+
call popup_close(self.winid)
369+
catch
370+
endtry
362371
else
363-
call nvim_win_close(self.winid, 1)
372+
try
373+
call nvim_win_close(self.winid, 1)
374+
catch
375+
endtry
364376
if self.info.border_winid >= 0
365-
call nvim_win_close(self.info.border_winid, 1)
377+
try
378+
call nvim_win_close(self.info.border_winid, 1)
379+
catch
380+
endtry
366381
let self.info.border_winid = -1
367382
endif
368383
endif
@@ -716,15 +731,21 @@ function! s:window.mouse_click()
716731
let retval.y = pos.line - 2
717732
endif
718733
else
719-
if v:mouse_winid != winid
720-
return retval
721-
endif
722-
if self.info.has_border == 0
723-
let retval.x = v:mouse_col - 1
724-
let retval.y = v:mouse_lnum - 1
725-
else
726-
let retval.x = v:mouse_col - 2
727-
let retval.y = v:mouse_lnum - 2
734+
if v:mouse_winid == winid
735+
if self.info.has_border == 0
736+
let retval.x = v:mouse_col - 1
737+
let retval.y = v:mouse_lnum - 1
738+
else
739+
let retval.x = v:mouse_col - 2
740+
let retval.y = v:mouse_lnum - 2
741+
endif
742+
elseif self.info.border_winid >= 0 && v:mouse_winid == self.info.border_winid
743+
" detect close button click on Neovim border window
744+
if get(self.opts, 'button', 0) != 0
745+
if v:mouse_lnum == 1 && v:mouse_col == self.info.tw
746+
let self.quit = 1
747+
endif
748+
endif
728749
endif
729750
endif
730751
return retval

test/test_dialog_auto.vim

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
"======================================================================
2+
"
3+
" test_dialog_auto.vim - automated (non-interactive) test for dialog
4+
"
5+
" Usage (recommended — ex silent mode with rtp pre-set):
6+
" vim -u NONE -N -i NONE -n --not-a-term -es
7+
" -c "set rtp+=c:/Share/vim"
8+
" -c "source c:/Share/vim/tools/test/test_dialog_auto.vim"
9+
" echo Exit code: %ERRORLEVEL%
10+
" type c:\Share\vim\test_dialog_result.log
11+
"
12+
" Also works via -S when <sfile> resolves correctly:
13+
" vim -u NONE -N -i NONE -n --not-a-term -es
14+
" -S tools/test/test_dialog_auto.vim
15+
"
16+
"======================================================================
17+
18+
" vim: set ts=4 sw=4 tw=78 noet :
19+
20+
21+
" ── 0. load dependencies ──────────────────────────────────
22+
" Try <sfile> first (works with -S), fall back to rtp scan.
23+
let s:home = fnamemodify(resolve(expand('<sfile>:p')), ':h:h:h')
24+
if !isdirectory(s:home . '/autoload/quickui')
25+
let s:home = ''
26+
for s:p in split(&rtp, ',')
27+
if isdirectory(s:p . '/autoload/quickui')
28+
let s:home = s:p
29+
break
30+
endif
31+
endfor
32+
endif
33+
if s:home != ''
34+
exec 'set rtp+=' . fnameescape(s:home)
35+
endif
36+
let s:logfile = (s:home != '' ? s:home : '.') . '/test_dialog_result.log'
37+
38+
39+
" ── 1. test helpers ───────────────────────────────────────
40+
let s:errors = []
41+
let s:passed = 0
42+
43+
function! s:assert_equal(expected, actual, msg) abort
44+
if a:expected != a:actual
45+
call add(s:errors, a:msg . ': expected ' .
46+
\ string(a:expected) . ', got ' . string(a:actual))
47+
else
48+
let s:passed += 1
49+
endif
50+
endfunc
51+
52+
53+
" ── 2. test: empty items ─────────────────────────────────
54+
let r = quickui#dialog#open([], {})
55+
call s:assert_equal('', r.button, 'empty: button')
56+
call s:assert_equal(-1, r.button_index, 'empty: index')
57+
58+
59+
" ── 3. test: ESC cancel preserves values ─────────────────
60+
call feedkeys("\<ESC>", 't')
61+
let r = quickui#dialog#open([
62+
\ {'type': 'input', 'name': 'name', 'prompt': 'Name:',
63+
\ 'value': 'test_value'},
64+
\ {'type': 'check', 'name': 'flag', 'text': 'Enable', 'value': 1},
65+
\ {'type': 'button', 'name': 'confirm',
66+
\ 'items': [' &OK ', ' &Cancel ']},
67+
\ ], {'title': 'Test', 'w': 40})
68+
call s:assert_equal('', r.button, 'ESC: button')
69+
call s:assert_equal(-1, r.button_index, 'ESC: index')
70+
call s:assert_equal('test_value', r.name, 'ESC: preserves input')
71+
call s:assert_equal(1, r.flag, 'ESC: preserves check')
72+
73+
74+
" ── 4. test: type in input then Enter confirms ───────────
75+
call feedkeys("hello\<CR>", 't')
76+
let r = quickui#dialog#open([
77+
\ {'type': 'input', 'name': 'x', 'prompt': 'X:'},
78+
\ {'type': 'button', 'name': 'ok',
79+
\ 'items': [' &OK ', ' &Cancel ']},
80+
\ ], {'title': 'Test', 'w': 40})
81+
call s:assert_equal('', r.button, 'input+Enter: button')
82+
call s:assert_equal(0, r.button_index, 'input+Enter: index')
83+
call s:assert_equal('hello', r.x, 'input+Enter: value')
84+
85+
86+
" ── 5. test: type in input, Tab to button, Enter ─────────
87+
call feedkeys("world\<Tab>\<CR>", 't')
88+
let r = quickui#dialog#open([
89+
\ {'type': 'input', 'name': 'x', 'prompt': 'X:'},
90+
\ {'type': 'button', 'name': 'ok',
91+
\ 'items': [' &OK ', ' &Cancel ']},
92+
\ ], {'title': 'Test', 'w': 40})
93+
call s:assert_equal('ok', r.button, 'Tab+Enter: button')
94+
call s:assert_equal(0, r.button_index, 'Tab+Enter: index')
95+
call s:assert_equal('world', r.x, 'Tab+Enter: value')
96+
97+
98+
" ── 6. test: radio Right navigation ──────────────────────
99+
" Right moves cursor, Space commits selection
100+
call feedkeys("\<Right>\<Right>\<Space>\<CR>", 't')
101+
let r = quickui#dialog#open([
102+
\ {'type': 'radio', 'name': 'r',
103+
\ 'items': ['A', 'B', 'C'], 'value': 0},
104+
\ ], {'title': 'Test', 'w': 40})
105+
call s:assert_equal('', r.button, 'radio: button')
106+
call s:assert_equal(2, r.r, 'radio: value after Right x2 + Space')
107+
108+
109+
" ── 7. test: check Space toggle ──────────────────────────
110+
call feedkeys("\<Space>\<CR>", 't')
111+
let r = quickui#dialog#open([
112+
\ {'type': 'check', 'name': 'c', 'text': 'Flag', 'value': 0},
113+
\ ], {'title': 'Test', 'w': 40})
114+
call s:assert_equal(1, r.c, 'check: toggled on')
115+
116+
117+
" ── 8. test: button hotkey from non-input control ────────
118+
" Initial focus on check (first focusable), press 'c' for &Cancel
119+
call feedkeys("c", 't')
120+
let r = quickui#dialog#open([
121+
\ {'type': 'check', 'name': 'f', 'text': 'Flag', 'value': 0},
122+
\ {'type': 'button', 'name': 'ok',
123+
\ 'items': [' &OK ', ' &Cancel ']},
124+
\ ], {'title': 'Test', 'w': 40})
125+
call s:assert_equal('ok', r.button, 'hotkey: button')
126+
call s:assert_equal(1, r.button_index, 'hotkey: Cancel index')
127+
128+
129+
" ── 9. test: Enter from check confirms ───────────────────
130+
call feedkeys("\<CR>", 't')
131+
let r = quickui#dialog#open([
132+
\ {'type': 'check', 'name': 'c', 'text': 'Flag', 'value': 0},
133+
\ {'type': 'button', 'name': 'ok',
134+
\ 'items': [' &OK ']},
135+
\ ], {'title': 'Test', 'w': 40})
136+
call s:assert_equal('', r.button, 'check+Enter: button')
137+
call s:assert_equal(0, r.button_index, 'check+Enter: index')
138+
139+
140+
" ── 10. test: label only (no focusable controls) ─────────
141+
call feedkeys("\<ESC>", 't')
142+
let r = quickui#dialog#open([
143+
\ {'type': 'label', 'text': 'Just a label'},
144+
\ ], {'title': 'Test', 'w': 40})
145+
call s:assert_equal('', r.button, 'label only: button')
146+
call s:assert_equal(-1, r.button_index, 'label only: index')
147+
148+
149+
" ── 11. test: prompt alignment ────────────────────────────
150+
" Two inputs with different prompt lengths should align
151+
call feedkeys("\<ESC>", 't')
152+
let r = quickui#dialog#open([
153+
\ {'type': 'input', 'name': 'a', 'prompt': 'Name:'},
154+
\ {'type': 'input', 'name': 'b', 'prompt': 'Email Address:'},
155+
\ ], {'title': 'Test', 'w': 50})
156+
call s:assert_equal('', r.button, 'align: button')
157+
call s:assert_equal('', r.a, 'align: a default')
158+
call s:assert_equal('', r.b, 'align: b default')
159+
160+
161+
" ── 12. test: no button control — Enter from radio ───────
162+
call feedkeys("\<CR>", 't')
163+
let r = quickui#dialog#open([
164+
\ {'type': 'radio', 'name': 'r',
165+
\ 'items': ['X', 'Y'], 'value': 1},
166+
\ ], {'title': 'Test', 'w': 40})
167+
call s:assert_equal('', r.button, 'no-btn: button')
168+
call s:assert_equal(0, r.button_index, 'no-btn: index')
169+
call s:assert_equal(1, r.r, 'no-btn: radio value unchanged')
170+
171+
172+
" ── 13. test: prompt alignment inflates width for check ───
173+
" A check with short prompt + wide text should not overflow when
174+
" aligned to an input with a long prompt.
175+
call feedkeys("\<ESC>", 't')
176+
let r = quickui#dialog#open([
177+
\ {'type': 'input', 'name': 'a', 'prompt': 'Very Long Prompt:',
178+
\ 'value': ''},
179+
\ {'type': 'check', 'name': 'b', 'prompt': 'P:',
180+
\ 'text': 'A checkbox label text here', 'value': 0},
181+
\ ], {'title': 'Test'})
182+
" The dialog should have opened and closed without error.
183+
" Verify that the width is at least prompt_width(aligned) + 4 + text_width.
184+
" 'Very Long Prompt:' display width = 17, aligned prompt_width = 17+2 = 19
185+
" check text 'A checkbox label text here' display width = 26
186+
" minimum width = 19 + 4 + 26 = 49
187+
" If the old code ran, w might be as small as 40 (min_w), causing overflow.
188+
call s:assert_equal('', r.button, 'align-inflate: button')
189+
call s:assert_equal(0, r.b, 'align-inflate: check value')
190+
call s:assert_equal('', r.a, 'align-inflate: input value')
191+
192+
193+
" ── report results ────────────────────────────────────────
194+
let total = s:passed + len(s:errors)
195+
if len(s:errors) == 0
196+
call writefile(['ALL PASSED (' . total . ' assertions)'], s:logfile)
197+
qa!
198+
else
199+
let report = ['FAILED: ' . len(s:errors) . '/' . total] + s:errors
200+
call writefile(report, s:logfile)
201+
cq 1
202+
endif

0 commit comments

Comments
 (0)