aboutsummaryrefslogtreecommitdiff
path: root/plugins/tab_switcher.lua
blob: 1144153aba8a8976ecd1acca00279444118d38b5 (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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
-- mod-version:3
local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
local common = require "core.common"
local DocView = require "core.docview"

local nodes_visit_order = setmetatable({}, {__mode = "k"})

---When true, releasing all the modifier keys will accept the selection
local hold_mode = false
---Whether or not the CommandView relative to this plugin is active
local in_switcher = false

---@param t table<any, boolean?>
---@return boolean
local function any_true(t)
  for _, v in pairs(t) do
    if v then return true end
  end
  return false
end

-- We need to override keymap.on_key_released to detect that all the modifier
-- keys were released while in "hold" mode, to accept the current selection.
local old_keymap_on_key_released = keymap.on_key_released
function keymap.on_key_released(k)
  -- Check if hold_mode has been triggered erroneously
  if hold_mode and not in_switcher then
    hold_mode = false
    core.warn("Something went wrong with the tab_switcher plugin. " ..
              "Please open an issue about it in the plugins repository on Github.")
  end

  local was_pressed = any_true(keymap.modkeys)
  old_keymap_on_key_released(k)
  local still_pressed = any_true(keymap.modkeys)

  if hold_mode and was_pressed and not still_pressed then
    hold_mode = false
    command.perform("command:submit")
  end
end


local order_counter = 0

local core_set_active_view = core.set_active_view
function core.set_active_view(view)
  nodes_visit_order[view] = order_counter
  order_counter = order_counter + 1
  return core_set_active_view(view)
end

local tab_switcher = {}

---@class tab_switcher.tab_item
---@field text string The tab name
---@field view core.view

---Returns the list of DocView tabs under a specific node tree.
---@param base_node core.node Where to start the search from
---@return tab_switcher.tab_item[]
function tab_switcher.get_tab_list(base_node)
  local raw_list = base_node:get_children()
  local list = {}
  local mt = {
    -- fuzzy_match uses tostring to get the text to compare
    __tostring = function(i) return i.text end
  }
  for _,v in pairs(raw_list) do
    if v:is(DocView) then
      table.insert(list, setmetatable({
        text = v:get_name(),
        view = v
      }, mt))
    end
  end
  table.sort(list, function(a, b)
    return (nodes_visit_order[a.view] or -1) > (nodes_visit_order[b.view] or -1)
  end)
  return list
end

local function set_active_view(view)
  local n = core.root_view.root_node:get_node_for_view(view)
  if n then n:set_active_view(view) end
end

---@param label string
---@param items tab_switcher.tab_item[]
local function ask_selection(label, items)
  in_switcher = true
  core.command_view:enter(label, {
    submit = function(_, item)
      in_switcher = false
      set_active_view(item.view)
    end,
    suggest = function(text)
      if #text > 1 then
        return common.fuzzy_match(items, text, true)
      else
        return items
      end
    end,
    validate = function(_, item)
      return item
    end,
    cancel = function()
      in_switcher = false
    end,
  })
end

command.add(function(items)
    items = items or tab_switcher.get_tab_list(core.root_view.root_node)
    return #items > 0, items
  end, {
  ["tab-switcher:tab-list"] = function(items)
    ask_selection("Switch to tab", items)
  end,
  ["tab-switcher:switch-to-last-tab"] = function(items)
    command.perform("tab-switcher:tab-list", items)
    command.perform("tab-switcher:previous-tab")
  end,
})

command.add(function(items)
    items = items or tab_switcher.get_tab_list(core.root_view:get_active_node())
    return #items > 0, items
  end, {
  ["tab-switcher:tab-list-current-split"] = function(items)
    ask_selection("Switch to tab in current split", items)
  end,
  ["tab-switcher:switch-to-last-tab-in-current-split"] = function(items)
    command.perform("tab-switcher:tab-list-current-split", items)
    command.perform("tab-switcher:previous-tab")
  end,
})

command.add(function() return in_switcher end, {
  ["tab-switcher:next-tab"] = function()
    hold_mode = true
    command.perform("command:select-next")
  end,
  ["tab-switcher:previous-tab"] = function()
    hold_mode = true
    command.perform("command:select-previous")
  end,
})

keymap.add({
  ["alt+p"]             = { "tab-switcher:previous-tab", "tab-switcher:tab-list" },
  ["ctrl+alt+p"]        = { "tab-switcher:previous-tab", "tab-switcher:switch-to-last-tab" },
  ["alt+shift+p"]       = { "tab-switcher:next-tab", "tab-switcher:tab-list-current-split" },
  ["ctrl+alt+shift+p"]  = { "tab-switcher:next-tab", "tab-switcher:switch-to-last-tab-in-current-split" },
})

return tab_switcher