aboutsummaryrefslogtreecommitdiff
path: root/plugins/regexreplacepreview.lua
blob: 18d692b141674dceb33cf2d2b8eef2ba16d7e7c0 (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
-- mod-version:3
local core = require "core"
local keymap = require "core.keymap"
local command = require "core.command"

-- Takes the following pattern: /pattern/replace/
-- Capture groupings can be replaced using \1 through \9
local function regex_replace_file(view, pattern, old_lines, raw, start_line, end_line)
  local doc = view.doc
  local start_pattern, end_pattern, end_replacement, start_replacement = 2, 2;
  repeat
    end_pattern = string.find(pattern, "/", end_pattern)
  until end_pattern == nil or pattern[end_pattern-1] ~= "\\"
  if end_pattern == nil then
    end_pattern = #pattern + 1
  else
    end_pattern = end_pattern - 1
    start_replacement = end_pattern+2;
    end_replacement = end_pattern+2;
    repeat
      end_replacement = string.find(pattern, "/", end_replacement)
    until end_replacement == nil or pattern[end_replacement-1] ~= "\\"
  end
  end_replacement = end_replacement and (end_replacement - 1)

  local re = start_pattern ~= end_pattern
    and regex.compile(pattern:sub(start_pattern, end_pattern))

  local replacement = end_replacement and pattern:sub(
    start_replacement, end_replacement
  )
  local replace_line = raw and function(line, new_text)
    if line == #doc.lines then
      doc:raw_remove(line, 1, line, #doc.lines[line], { idx = 1 }, 0)
    else
      doc:raw_remove(line, 1, line+1, 1, { idx = 1 }, 0)
    end
    doc:raw_insert(line, 1, new_text, { idx = 1 }, 0)
  end or function(line, new_text)
    if line == #doc.lines then
      doc:remove(line, 1, line, #doc.lines[line])
    else
      doc:remove(line, 1, line+1, 1)
    end
    doc:insert(line, 1, new_text)
  end

  local line_scroll = nil
  if re then
    for i = (start_line or 1), (end_line or #doc.lines) do
      local new_text, matches, rmatches
      local old_text = old_lines[i] or doc.lines[i]
      local old_length = #old_text
      if replacement then
        new_text, matches, rmatches = regex.gsub(re, old_text, replacement)
      end
      if matches and #matches > 0 then
        old_lines[i] = old_text
        replace_line(i, new_text)
        if line_scroll == nil then
          line_scroll = i
          doc:set_selection(i, rmatches[1][1], i, rmatches[1][2])
        end
      elseif old_lines[i] then
        replace_line(i, old_lines[i])
        old_lines[i] = nil
      end
      if not replacement then
        local s,e = regex.match(re, old_text)
        if s then
          line_scroll = i
          doc:set_selection(i, s, i, e)
          break
        end
      end
    end
    if line_scroll then
      view:scroll_to_line(line_scroll, true)
    end
  end
  if replacement == nil then
    for k,v in pairs(old_lines) do
      replace_line(k, v)
    end
    old_lines = {}
  end
  return old_lines, line_scroll ~= nil
end

command.add("core.docview", {
  ["regex-replace-preview:find-replace-regex"] = function()
    local old_lines = {}
    local view = core.active_view
    local doc = view.doc
    local original_selection = { doc:get_selection(true) }
    local selection = doc:has_selection() and { doc:get_selection(true) } or {}
    core.command_view:enter("Regex Replace (enter pattern as /old/new/)", {
      text = "/",
      submit = function(pattern)
        regex_replace_file(view, pattern, {}, false, selection[1], selection[3])
      end,
      suggest = function(pattern)
        local incremental, has_replacement = regex_replace_file(
          view, pattern, old_lines, true, selection[1], selection[3]
        )
        if incremental then
          old_lines = incremental
        end
        if not has_replacement then
          doc:set_selection(table.unpack(original_selection))
        end
      end,
      cancel = function(pattern)
        for k,v in pairs(old_lines) do
          if v then
            if k == #doc.lines then
              doc:raw_remove(k, 1, k, #doc.lines[k], { idx = 1 }, 0)
            else
              doc:raw_remove(k, 1, k+1, 1, { idx = 1 }, 0)
            end
            doc:raw_insert(k, 1, v, { idx = 1 }, 0)
          end
        end
        doc:set_selection(table.unpack(original_selection))
      end
    })
  end
})

keymap.add { ["ctrl+shift+r"] = "regex-replace-preview:find-replace-regex" }