馃惢 minimal ui2 fuzzy finder for Neovim codeberg.org/comfysage/artio.nvim
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 823 lines 21 kB view raw
1local cmdline = require("vim._core.ui2.cmdline") 2local ui2 = require("vim._core.ui2") 3 4local _log = {} 5local _loglevel = vim.log.levels.ERROR 6---@private 7---@param msgwrapped { [1]: string, [2]: string? } 8---@param level? integer 9local function logadd(msgwrapped, level) 10 level = level or vim.log.levels.DEBUG 11 if level < _loglevel then 12 return 13 end 14 _log[#_log + 1] = msgwrapped 15end 16---@private 17---@param msg string 18local function logdebug(msg) 19 logadd({ msg .. "\n" }) 20end 21---@private 22---@param msg string 23---@param v any 24local function logdbg(msg, v) 25 logdebug(string.format("%s: %s\n", msg, (vim.inspect(v)))) 26end 27---@private 28---@param msg string 29local function logerror(msg) 30 logadd({ msg .. "\n", "ErrorMsg" }, vim.log.levels.ERROR) 31end 32 33local prompt_hl_id = vim.api.nvim_get_hl_id_by_name("ArtioPrompt") 34 35---@class artio.View 36---@field picker artio.Picker 37---@field closed boolean 38---@field opts table<'win'|'buf'|'g',table<string,any>> 39---@field marks table<string|integer, integer> 40---@field win artio.View.win 41---@field preview_win integer 42local View = {} 43View.__index = View 44 45---@param picker artio.Picker 46function View:new(picker) 47 ---@diagnostic disable-next-line: undefined-field 48 if picker.log_level then 49 ---@diagnostic disable-next-line: undefined-field 50 _loglevel = picker.log_level 51 end 52 53 return setmetatable({ 54 picker = picker, 55 closed = false, 56 opts = {}, 57 marks = {}, 58 win = { 59 height = 1, 60 }, 61 }, View) 62end 63 64---@class artio.View.win 65---@field height integer 66 67local prompthl_id = -1 68 69--- gets updated before draw 70local before_draw_tick = 0 71--- gets updated after changedtick event 72local last_draw_tick = 0 73local function get_changedtick() 74 return vim.api.nvim_buf_get_changedtick(ui2.bufs.cmd) 75end 76 77local cmdbuff = "" ---@type string Stored cmdline used to calculate translation offset. 78local promptlen = 0 -- Current length of the last line in the prompt. 79local promptidx = 0 80--- Concatenate content chunks and set the text for the current row in the cmdline buffer. 81--- 82---@param content CmdContent 83---@param prompt string 84function View:setprompttext(content, prompt) 85 local lines = {} ---@type string[] 86 for line in (prompt .. "\n"):gmatch("(.-)\n") do 87 lines[#lines + 1] = vim.fn.strtrans(line) 88 end 89 90 local promptstr = lines[#lines] 91 promptlen = #lines[#lines] 92 93 cmdbuff = "" 94 for _, chunk in ipairs(content) do 95 cmdbuff = cmdbuff .. chunk[2] 96 end 97 lines[#lines] = ("%s%s"):format(promptstr, vim.fn.strtrans(cmdbuff)) 98 99 self:promptpos() 100 self:setlines(promptidx, promptidx + 1, lines) 101 if vim.fn.prompt_getprompt(ui2.bufs.cmd) ~= promptstr then 102 vim.fn.prompt_setprompt(ui2.bufs.cmd, promptstr) 103 end 104 vim.schedule(function() 105 local ok, result = pcall(vim.api.nvim_buf_set_mark, ui2.bufs.cmd, ":", promptidx + 1, promptlen, {}) 106 if not ok then 107 logerror(("Failed to set mark %d:%d\n\t%s"):format(promptidx, promptlen, result)) 108 return 109 end 110 end) 111end 112 113--- Set the cmdline buffer text and cursor position. 114--- 115---@param content CmdContent 116---@param pos? integer 117---@param firstc string 118---@param prompt string 119---@param indent integer 120---@param level integer 121---@param hl_id integer 122function View:show(content, pos, firstc, prompt, indent, level, hl_id) 123 cmdline.level, cmdline.indent = level, indent 124 if cmdline.highlighter and cmdline.highlighter.active then 125 cmdline.highlighter.active[ui2.bufs.cmd] = nil 126 end 127 if ui2.msg.cmd.msg_row ~= -1 then 128 ui2.msg.msg_clear() 129 end 130 ui2.msg.virt.last = { {}, {}, {}, {} } 131 132 self:clear() 133 prompthl_id = hl_id 134 135 local cmd_text = "" 136 for _, chunk in ipairs(content) do 137 cmd_text = cmd_text .. chunk[2] 138 end 139 140 self:showmatches() 141 142 self:setprompttext(content, ("%s%s%s"):format(firstc, prompt, (" "):rep(indent))) 143 self:updatecursor(pos) 144 145 self:updatewinheight() 146 147 self:drawprompt() 148 self:hlselect() 149end 150 151--- Set the 'cmdheight' and cmdline window height. Reposition message windows. 152--- 153---@param win integer Cmdline window in the current tabpage. 154---@param hide boolean Whether to hide or show the window. 155---@param height integer (Text)height of the cmdline window. 156function View:win_config(win, hide, height) 157 if ui2.cmdheight == 0 and vim.api.nvim_win_get_config(win).hide ~= hide then 158 vim.api.nvim_win_set_config(win, { hide = hide, height = not hide and height or nil }) 159 elseif vim.api.nvim_win_get_height(win) ~= height then 160 vim.api.nvim_win_set_height(win, height) 161 end 162 163 if not hide and self.picker.win.hidestatusline then 164 height = 0 165 end 166 167 if vim.o.cmdheight ~= height then 168 -- Avoid moving the cursor with 'splitkeep' = "screen", and altering the user 169 -- configured value with noautocmd. 170 vim._with({ noautocmd = true, o = { splitkeep = "screen" } }, function() 171 vim.o.cmdheight = height 172 end) 173 ui2.msg.set_pos() 174 end 175 176 if self.preview_win and vim.api.nvim_win_is_valid(self.preview_win) then 177 vim.api.nvim_win_set_config(self.preview_win, self:previewconfig()) 178 end 179end 180 181---@param predicted? integer The predicted height of the cmdline window 182function View:updatewinheight(predicted) 183 local height = math.max(1, predicted or vim.api.nvim_win_text_height(ui2.wins.cmd, {}).all) 184 height = math.min(height, self.win.height) 185 self:win_config(ui2.wins.cmd, false, height) 186end 187 188function View:saveview() 189 self.save = vim.fn.winsaveview() 190 self.prevwin = vim.api.nvim_get_current_win() 191end 192 193function View:restoreview() 194 vim.api.nvim_set_current_win(self.prevwin) 195 vim.fn.winrestview(self.save) 196end 197 198local ext_winhl = "Search:,CurSearch:,IncSearch:" 199 200---@param restore? boolean 201function View:setopts(restore) 202 local opts = { 203 win = { 204 eventignorewin = "all,-FileType,-InsertCharPre,-TextChangedI,-CursorMovedI", 205 winhighlight = "Normal:ArtioNormal," .. ext_winhl, 206 signcolumn = "no", 207 wrap = false, 208 }, 209 buf = { 210 filetype = "artio-picker", 211 buftype = "prompt", 212 autocomplete = false, 213 }, 214 g = { 215 showmode = false, 216 showcmd = false, 217 }, 218 } 219 220 for level, o in pairs(opts) do 221 self.opts[level] = self.opts[level] or {} 222 local props = { 223 scope = level == "g" and "global" or "local", 224 buf = level == "buf" and ui2.bufs.cmd or nil, 225 win = level == "win" and ui2.wins.cmd or nil, 226 } 227 228 for name, value in pairs(o) do 229 if restore then 230 vim.api.nvim_set_option_value(name, self.opts[level][name], props) 231 else 232 self.opts[level][name] = vim.api.nvim_get_option_value(name, props) 233 vim.api.nvim_set_option_value(name, value, props) 234 end 235 end 236 end 237end 238 239local maxlistheight = 1 -- Max height of the matches list (`self.win.height - 1`) 240 241function View:on_resized() 242 logdebug("on_resized") 243 244 if self.picker.win.height > 1 then 245 self.win.height = self.picker.win.height 246 else 247 self.win.height = vim.o.lines * self.picker.win.height 248 end 249 self.win.height = math.max(math.ceil(self.win.height), 1) 250 251 maxlistheight = math.max(self.win.height - 1, 1) 252end 253 254function View:open() 255 if not self.picker then 256 return 257 end 258 _log = nil 259 _log = {} 260 261 ui2.check_targets() 262 263 vim.schedule(function() 264 self.augroup = vim.api.nvim_create_augroup("@artio.view", { clear = true }) 265 266 vim.api.nvim_create_autocmd("CmdlineLeave", { 267 group = self.augroup, 268 once = true, 269 callback = function() 270 self:close() 271 end, 272 }) 273 274 vim.api.nvim_create_autocmd("ModeChanged", { 275 group = self.augroup, 276 callback = function(ev) 277 if string.match(ev.match, "^i:") then 278 self:close() 279 end 280 end, 281 }) 282 283 vim.api.nvim_create_autocmd({ "VimResized", "WinEnter" }, { 284 group = self.augroup, 285 callback = function() 286 self:on_resized() 287 end, 288 }) 289 290 vim.api.nvim_create_autocmd("WinEnter", { 291 group = self.augroup, 292 callback = function() 293 self:update(true) 294 end, 295 }) 296 297 vim.api.nvim_create_autocmd("TextChangedI", { 298 group = self.augroup, 299 buffer = ui2.bufs.cmd, 300 callback = function() 301 self:update() 302 end, 303 }) 304 305 vim.api.nvim_create_autocmd("CursorMovedI", { 306 group = self.augroup, 307 buffer = ui2.bufs.cmd, 308 callback = function() 309 self:updatecursor() 310 end, 311 }) 312 end) 313 314 cmdline.prompt = false 315 cmdline.srow = 0 316 cmdline.indent = 1 317 cmdline.level = 1 318 319 self:saveview() 320 321 -- initial render 322 self:trigger_show() 323 324 vim._with({ noautocmd = true }, function() 325 vim.api.nvim_set_current_win(ui2.wins.cmd) 326 end) 327 328 self:setopts() 329 330 -- start insert *before* registering events 331 self:updatecursor(#cmdbuff) 332 vim._with({ noautocmd = true }, function() 333 vim.cmd.startinsert({ bang = true }) 334 end) 335 336 -- trigger after registering events 337 vim.schedule(function() 338 vim._with({ win = ui2.wins.cmd, wo = { eventignorewin = "" } }, function() 339 vim.api.nvim_exec_autocmds("WinEnter", {}) 340 end) 341 end) 342end 343 344function View:close() 345 if self.closed then 346 return 347 end 348 self:closepreview() 349 vim.schedule(function() 350 pcall(vim.api.nvim_del_augroup_by_id, self.augroup) 351 pcall(vim.api.nvim_buf_detach, ui2.bufs.cmd) 352 353 vim.cmd.stopinsert() 354 355 -- prepare state 356 self:setopts(true) 357 358 -- reset state 359 self:clear() 360 cmdline.srow = 0 361 cmdline.erow = 0 362 363 -- restore ui 364 self:hide() 365 self:restoreview() 366 vim.cmd.redraw() 367 368 self.closed = true 369 370 self.picker:close() 371 372 vim.api.nvim_echo(_log, true, {}) 373 end) 374end 375 376function View:hide() 377 vim.fn.clearmatches(ui2.wins.cmd) -- Clear matchparen highlights. 378 vim.api.nvim_win_set_cursor(ui2.wins.cmd, { 1, 0 }) 379 vim.api.nvim_buf_set_lines(ui2.bufs.cmd, 0, -1, false, {}) 380 381 local clear = vim.schedule_wrap(function(was_prompt) 382 -- Avoid clearing prompt window when it is re-entered before the next event 383 -- loop iteration. E.g. when a non-choice confirm button is pressed. 384 if was_prompt and not cmdline.prompt then 385 pcall(function() 386 vim.api.nvim_buf_set_lines(ui2.bufs.cmd, 0, -1, false, {}) 387 vim.api.nvim_buf_set_lines(ui2.bufs.dialog, 0, -1, false, {}) 388 vim.api.nvim_win_set_config(ui2.wins.dialog, { hide = true }) 389 vim.on_key(nil, ui2.msg.dialog_on_key) 390 end) 391 end 392 -- Messages emitted as a result of a typed command are treated specially: 393 -- remember if the cmdline was used this event loop iteration. 394 -- NOTE: Message event callbacks are themselves scheduled, so delay two iterations. 395 vim.schedule(function() 396 cmdline.level = -1 397 end) 398 end) 399 clear(cmdline.prompt) 400 401 cmdline.prompt, cmdline.level = false, 0 402 self:win_config(ui2.wins.cmd, true, ui2.cmdheight) 403end 404 405function View:trigger_show() 406 logdebug("trigger_show") 407 local input 408 if self.picker.live then 409 input = self.picker.liveinput 410 else 411 input = self.picker.input 412 end 413 self:show({ { 0, input } }, -1, "", self.picker.prompttext, cmdline.indent, cmdline.level, prompt_hl_id) 414end 415 416---@param force? boolean 417function View:update(force) 418 if not force and before_draw_tick < last_draw_tick and before_draw_tick == get_changedtick() - 1 then 419 logdebug("update (skip-redraw)") 420 return self:drawprompt() 421 end 422 423 logdebug("update") 424 425 local text = vim.api.nvim_get_current_line() 426 text = text:sub(promptlen + 1) 427 428 if self.picker.live then 429 self.picker.liveinput = text 430 else 431 self.picker.input = text 432 end 433 434 vim.schedule(coroutine.wrap(function() 435 logdebug("getmatches") 436 self.picker:getmatches() 437 438 if self.closed then 439 return 440 end 441 442 vim.schedule_wrap(self.trigger_show)(self) 443 end)) 444end 445 446local curpos = { 0, 0 } -- Last drawn cursor position. absolute 447---@param pos? integer relative to prompt 448function View:updatecursor(pos) 449 logdebug("updatecursor") 450 451 self:promptpos() 452 453 if not pos or pos < 0 then 454 local cursorpos = vim.api.nvim_win_get_cursor(ui2.wins.cmd) 455 pos = cursorpos[2] - promptlen 456 end 457 458 -- set cursor pos to *at least* the prompt length 459 curpos[2] = math.max(curpos[2], promptlen) 460 461 if curpos[1] == promptidx + 1 and curpos[2] == promptlen + pos then 462 return 463 end 464 465 if pos < 0 then 466 -- reset to last known position 467 pos = curpos[2] - promptlen 468 end 469 470 curpos[1], curpos[2] = promptidx + 1, promptlen + pos 471 472 vim._with({ noautocmd = true }, function() 473 local ok, _ = pcall(vim.api.nvim_win_set_cursor, ui2.wins.cmd, curpos) 474 if not ok then 475 logerror(("Failed to set cursor %d:%d"):format(curpos[1], curpos[2])) 476 end 477 end) 478end 479 480local srow = 0 481 482function View:clear() 483 srow = self.picker.opts.bottom and 0 or 1 484 cmdline.erow = srow 485 self:setlines(0, -1, {}) 486end 487 488function View:promptpos() 489 promptidx = self.picker.opts.bottom and cmdline.erow or 0 490end 491 492function View:setlines(posstart, posend, lines) 493 -- update winheight to prevent wrong scroll when increasing from 1 494 local diff = #lines - (posend - posstart) 495 if diff ~= 0 then 496 local height = vim.api.nvim_win_text_height(ui2.wins.cmd, {}).all 497 local predicted = height + diff 498 self:updatewinheight(predicted) 499 end 500 501 before_draw_tick = get_changedtick() 502 vim.api.nvim_buf_set_lines(ui2.bufs.cmd, posstart, posend, false, lines) 503 last_draw_tick = get_changedtick() 504end 505 506local view_ns = vim.api.nvim_create_namespace("@artio.view.ns") 507local ext_priority = { 508 prompt = 1, 509 info = 2, 510 select = 4, 511 marker = 8, 512 hl = 16, 513 icon = 32, 514 match = 64, 515} 516 517---@param id? string|integer 518---@param line integer 0-based 519---@param col integer 0-based 520---@param opts vim.api.keyset.set_extmark 521---@return integer 522function View:mark(id, line, col, opts) 523 if id and self.marks[id] then 524 vim._with({ noautocmd = true }, function() 525 vim.api.nvim_buf_del_extmark(ui2.bufs.cmd, view_ns, self.marks[id]) 526 end) 527 self.marks[id] = nil 528 end 529 530 opts.hl_mode = "combine" 531 opts.invalidate = true 532 533 local ok, result 534 vim._with({ noautocmd = true }, function() 535 ok, result = pcall(vim.api.nvim_buf_set_extmark, ui2.bufs.cmd, view_ns, line, col, opts) 536 end) 537 if not ok then 538 logerror(("Failed to add extmark %d:%d\n\t%s"):format(line, col, result)) 539 return -1 540 end 541 542 if id and result >= 0 then 543 self.marks[id] = result 544 end 545 546 return result 547end 548 549---@param p artio.Picker 550---@param info 'index'|'list'|string 551---@return string 552local function getpromptinfo(p, info) 553 if info == "index" then 554 return ("[%d]"):format(p.idx) 555 elseif info == "list" then 556 return ("(%d/%d)"):format(#p.matches, #p.items) 557 end 558 return "" 559end 560 561function View:drawprompt() 562 logdebug("drawprompt") 563 564 self:promptpos() 565 if promptlen > 0 and prompthl_id > 0 then 566 self:mark("prompthl", promptidx, 0, { hl_group = prompthl_id, end_col = promptlen, priority = ext_priority.prompt }) 567 self:mark("promptinfo", promptidx, 0, { 568 virt_text = { 569 { 570 table.concat( 571 vim 572 .iter(self.picker.opts.infolist) 573 :map(function(info) 574 return getpromptinfo(self.picker, info) 575 end) 576 :totable(), 577 " " 578 ), 579 "InfoText", 580 }, 581 }, 582 virt_text_pos = "eol_right_align", 583 priority = ext_priority.info, 584 }) 585 end 586end 587 588local offset = 0 589 590function View:updateoffset() 591 self.picker:fix() 592 if self.picker.idx == 0 then 593 offset = 0 594 return 595 end 596 597 local _offset = self.picker.idx - maxlistheight 598 if _offset > offset then 599 offset = _offset 600 elseif self.picker.idx <= offset then 601 offset = self.picker.idx - 1 602 end 603 604 offset = math.min(math.max(0, offset), math.max(0, #self.picker.matches - maxlistheight)) 605end 606 607local icon_pad = 2 608 609function View:showmatches() 610 local indent = vim.fn.strdisplaywidth(self.picker.opts.pointer) + 1 611 local prefix = (" "):rep(indent) 612 local icon_pad_str = (" "):rep(icon_pad) 613 614 self:updateoffset() 615 616 local lines = {} ---@type string[] 617 local hls = {} 618 local icons = {} ---@type ([string, string]|false)[] 619 local custom_hls = {} ---@type (artio.Picker.hl[]|false)[] 620 local marks = {} ---@type boolean[] 621 for i = 1 + offset, math.min(#self.picker.matches, maxlistheight + offset) do 622 local match = self.picker.matches[i] 623 local item = self.picker.items[match[1]] 624 625 local icon, icon_hl = item.icon, item.icon_hl 626 if not (icon and icon_hl) and vim.is_callable(self.picker.get_icon) then 627 icon, icon_hl = self.picker.get_icon(item) 628 item.icon, item.icon_hl = icon, icon_hl 629 end 630 icons[#icons + 1] = icon and { icon, icon_hl } or false 631 icon = icon and ("%s%s"):format(item.icon, icon_pad_str) or "" 632 633 local hl = item.hls 634 if not hl and vim.is_callable(self.picker.hl_item) then 635 hl = self.picker.hl_item(item) 636 item.hls = hl 637 end 638 custom_hls[#custom_hls + 1] = hl or false 639 640 marks[#marks + 1] = self.picker.marked[item.id] or false 641 642 lines[#lines + 1] = ("%s%s%s"):format(prefix, icon, item.text) 643 hls[#hls + 1] = match[2] 644 end 645 646 if not self.picker.opts.shrink then 647 for _ = 1, (maxlistheight - #lines) do 648 lines[#lines + 1] = "" 649 end 650 end 651 self:setlines(srow, cmdline.erow, lines) 652 cmdline.erow = srow + #lines 653 654 for i = 1, #lines do 655 local has_icon = icons[i] and icons[i][1] and true 656 local icon_indent = has_icon and (#icons[i][1] + icon_pad) or 0 657 658 if has_icon and icons[i][2] then 659 self:mark(nil, srow + i - 1, indent, { 660 end_col = indent + icon_indent, 661 hl_group = icons[i][2], 662 priority = ext_priority.icon, 663 }) 664 end 665 666 local line_hls = custom_hls[i] 667 if line_hls then 668 for j = 1, #line_hls do 669 local hl = line_hls[j] 670 self:mark(nil, srow + i - 1, indent + icon_indent + hl[1][1], { 671 end_col = indent + icon_indent + hl[1][2], 672 hl_group = hl[2], 673 priority = ext_priority.hl, 674 }) 675 end 676 end 677 678 if marks[i] then 679 self:mark(nil, srow + i - 1, indent - 1, { 680 virt_text = { { self.picker.opts.marker, "ArtioMark" } }, 681 virt_text_pos = "overlay", 682 priority = ext_priority.marker, 683 }) 684 self:mark(nil, srow + i - 1, 0, { 685 hl_group = "ArtioMarkLine", 686 hl_eol = true, 687 end_row = srow + i, 688 end_col = 0, 689 690 priority = ext_priority.marker, 691 }) 692 end 693 694 if hls[i] then 695 for j = 1, #hls[i] do 696 local col = indent + icon_indent + hls[i][j] 697 self:mark(nil, srow + i - 1, col, { 698 hl_group = "ArtioMatch", 699 end_col = col + 1, 700 priority = ext_priority.match, 701 }) 702 end 703 end 704 end 705end 706 707function View:hlselect() 708 self:softupdatepreview() 709 710 self.picker:fix() 711 local idx = self.picker.idx 712 if idx == 0 then 713 return 714 end 715 716 self:updateoffset() 717 local row = math.max(0, math.min(srow + (idx - offset), cmdline.erow) - 1) 718 719 self:mark("hlselect", row, 0, { 720 virt_text = { { self.picker.opts.pointer, "ArtioPointer" } }, 721 virt_text_pos = "overlay", 722 723 hl_group = "ArtioSel", 724 hl_eol = true, 725 end_row = row + 1, 726 end_col = 0, 727 728 priority = ext_priority.select, 729 }) 730end 731 732function View:togglepreview() 733 if self.preview_win then 734 self:closepreview() 735 return 736 end 737 738 self:updatepreview() 739end 740 741---@return {buf?:integer, pos?:[integer,integer], pos_end?:[integer,integer]}? 742function View:openpreview() 743 if self.picker.idx == 0 then 744 return 745 end 746 747 local match = self.picker.matches[self.picker.idx] 748 local item = self.picker.items[match[1]] 749 750 if not item or not (self.picker.preview_item and vim.is_callable(self.picker.preview_item)) then 751 return 752 end 753 754 return self.picker.preview_item(item.v) 755end 756 757function View:previewconfig() 758 local previewopts = self.picker.win.preview_opts 759 and vim.is_callable(self.picker.win.preview_opts) 760 and self.picker.win.preview_opts(self) 761 local cmdheight = vim.api.nvim_win_get_height(ui2.wins.cmd) 762 763 local winborder = previewopts and previewopts.border or vim.o.winborder 764 return vim.tbl_extend("force", { 765 relative = "editor", 766 focusable = false, 767 width = vim.o.columns, 768 height = self.win.height, 769 col = 0, 770 row = vim.o.lines 771 - (self.win.height + cmdheight) 772 - ((winborder == "none" or winborder == "") and 0 or 2) 773 - (self.picker.win.hidestatusline and 0 or 1), 774 }, previewopts or {}) 775end 776 777function View:updatepreview() 778 local pr = self:openpreview() 779 if not pr or not pr.buf then 780 return 781 end 782 783 if not self.preview_win then 784 self.preview_win = vim.api.nvim_open_win(pr.buf, false, self:previewconfig()) 785 else 786 vim.api.nvim_win_set_buf(self.preview_win, pr.buf) 787 end 788 789 vim._with({ win = self.preview_win, noautocmd = true }, function() 790 vim.api.nvim_set_option_value("previewwindow", true, { scope = "local" }) 791 vim.api.nvim_set_option_value("eventignorewin", "all,-FileType", { scope = "local" }) 792 793 local sameline = pr.pos ~= nil and pr.pos_end == nil or pr.pos_end[1] == pr.pos[1] 794 vim.api.nvim_set_option_value("cursorline", sameline, { scope = "local" }) 795 796 if pr.pos then 797 vim.api.nvim_win_set_cursor(self.preview_win, pr.pos) 798 end 799 end) 800end 801 802function View:softupdatepreview() 803 if self.picker.idx == 0 then 804 self:closepreview() 805 end 806 807 if not self.preview_win then 808 return 809 end 810 811 self:updatepreview() 812end 813 814function View:closepreview() 815 if not self.preview_win then 816 return 817 end 818 819 vim.api.nvim_win_close(self.preview_win, true) 820 self.preview_win = nil 821end 822 823return View