summaryrefslogtreecommitdiff
path: root/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/cmdline_events.lua
blob: 6f23ed83a68df9e34a386e5c78a3beabf1504199 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
--- @class blink.cmp.CmdlineEvents
--- @field has_context fun(): boolean
--- @field ignore_next_text_changed boolean
--- @field ignore_next_cursor_moved boolean
---
--- @field new fun(): blink.cmp.CmdlineEvents
--- @field listen fun(self: blink.cmp.CmdlineEvents, opts: blink.cmp.CmdlineEventsListener)
--- @field suppress_events_for_callback fun(self: blink.cmp.CmdlineEvents, cb: fun())

--- @class blink.cmp.CmdlineEventsListener
--- @field on_char_added fun(char: string, is_ignored: boolean)
--- @field on_cursor_moved fun(event: 'CursorMoved' | 'InsertEnter', is_ignored: boolean)
--- @field on_leave fun()

--- @type blink.cmp.CmdlineEvents
--- @diagnostic disable-next-line: missing-fields
local cmdline_events = {}

function cmdline_events.new()
  return setmetatable({
    ignore_next_text_changed = false,
    ignore_next_cursor_moved = false,
  }, { __index = cmdline_events })
end

function cmdline_events:listen(opts)
  -- TextChanged
  local on_changed = function(key) opts.on_char_added(key, false) end

  -- We handle backspace as a special case, because the text will have changed
  -- but we still want to fire the CursorMoved event, and not the TextChanged event
  local did_backspace = false
  local is_change_queued = false
  vim.on_key(function(raw_key, escaped_key)
    if vim.api.nvim_get_mode().mode ~= 'c' then return end

    -- ignore if it's a special key
    -- FIXME: odd behavior when escaped_key has multiple keycodes, i.e. by pressing <C-p> and then "t"
    local key = vim.fn.keytrans(escaped_key)
    if key == '<BS>' and not is_change_queued then did_backspace = true end
    if key:sub(1, 1) == '<' and key:sub(#key, #key) == '>' and raw_key ~= ' ' then return end
    if key == '' then return end

    if not is_change_queued then
      is_change_queued = true
      did_backspace = false
      vim.schedule(function()
        on_changed(raw_key)
        is_change_queued = false
      end)
    end
  end)

  -- CursorMoved
  local previous_cmdline = ''
  vim.api.nvim_create_autocmd('CmdlineEnter', {
    callback = function() previous_cmdline = '' end,
  })

  -- TODO: switch to CursorMovedC when nvim 0.11 is released
  -- HACK: check every 16ms (60 times/second) to see if the cursor moved
  -- for neovim < 0.11
  local timer = vim.uv.new_timer()
  local previous_cursor
  local callback
  callback = vim.schedule_wrap(function()
    timer:start(16, 0, callback)
    if vim.api.nvim_get_mode().mode ~= 'c' then return end

    local cmdline_equal = vim.fn.getcmdline() == previous_cmdline
    local cursor_equal = vim.fn.getcmdpos() == previous_cursor

    previous_cmdline = vim.fn.getcmdline()
    previous_cursor = vim.fn.getcmdpos()

    if cursor_equal or (not cmdline_equal and not did_backspace) then return end
    did_backspace = false

    local is_ignored = self.ignore_next_cursor_moved
    self.ignore_next_cursor_moved = false

    opts.on_cursor_moved('CursorMoved', is_ignored)
  end)
  timer:start(16, 0, callback)

  vim.api.nvim_create_autocmd('CmdlineLeave', {
    callback = function() opts.on_leave() end,
  })
end

--- Suppresses autocmd events for the duration of the callback
--- HACK: there's likely edge cases with this
function cmdline_events:suppress_events_for_callback(cb)
  local cursor_before = vim.fn.getcmdpos()

  cb()

  if not vim.api.nvim_get_mode().mode == 'c' then return end

  local cursor_after = vim.fn.getcmdpos()
  self.ignore_next_cursor_moved = self.ignore_next_cursor_moved or cursor_after ~= cursor_before
end

return cmdline_events