aboutsummaryrefslogtreecommitdiff
path: root/plugins/indent_convert.lua
blob: 2442b39facda52e5c828a11fd23ec59e91cd56b4 (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
-- mod-version:3 --lite-xl 2.1
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local command = require "core.command"

config.plugins.indent_convert = common.merge({
  update_indent_type = true -- set to false to avoid updating the document indent type
}, config.plugins.indent_convert)

local zero_pattern = _VERSION == "Lua 5.1" and "%z" or "\0"

-- TODO: only set document indent type if there are no selections
-- TODO: correctly restore selections accounting for the offset caused by the conversion

-- To replace N spaces with tabs, we match the last N spaces before the start of
-- the actual code and replace them with a tab.
-- We repeat this until we can't find any more spaces before the code.
-- The problem we encounter with this method is that if we have less than N
-- remaining spaces, those will end up at the start of the line.
-- Eg:
-- int main() {
-- __printf("Hello world\n");
-- ___return 0;
-- }
--
-- Becomes
-- int main() {
-- #printf("Hello world\n");
-- _#return 0;
-- }
--
-- Instead of
-- int main() {
-- #printf("Hello world\n");
-- #_return 0;
-- }
-- With regex we could do something like
-- `regex.gsub("(^(?: {2})*)(?: {2})", "\\1\t")`
-- but the implementation of `regex.gsub` is very slow.
--
-- The workaround is to find the longest possible repetition of N*X spaces and
-- use that information to replace the longest repetition of spaces starting
-- from the beginning of the line, then the second longest...
local function spaces_replacer(text, indent_size)
  local spaces = string.rep(" ", indent_size)
  local total = 0
  local n
  local reps = 0
  -- find the longest repetition of indent_size*spaces
  repeat
    reps = reps + 1
    local s, _ = string.find(text, "%f[^"..zero_pattern.."\n]"..string.rep(spaces, reps))
  until not s
  reps = reps - 1
  while reps > 0 do
    text, n = string.gsub(text,
                          "(%f[^"..zero_pattern.."\n])("..string.rep(spaces, reps)..")",
                          "%1"..string.rep("\t", reps))
    total = total + n
    reps = reps - 1
  end
  return text, total
end

local function tabs_replacer(text, indent_size)
  local spaces = string.rep(" ", indent_size)
  local total = 0
  local n
  -- replace the last tab before the text until there aren't anymore
  repeat
    text, n = string.gsub(text, "(%f[^"..zero_pattern.."\n]\t*)(\t)", "%1"..spaces)
    total = total + n
  until n == 0
  return text, total
end

local function replacer(doc, fn, indent_size)
  return function(text)
    return fn(text, indent_size)
  end
end

local function get_indent_size(doc)
  local indent_size = config.indent_size
  if type(doc.get_indent_info) == "function" then
    -- use the new `Doc:get_indent_info` function
    indent_size = select(2, doc:get_indent_info())
  end
  return indent_size
end

local function tabs_to_spaces()
  local doc = core.active_view.doc
  local indent_size = get_indent_size(doc)
  local selections = doc.selections
  doc:replace(replacer(doc, tabs_replacer, indent_size))
  doc.selections = selections
  doc:sanitize_selection()
  if config.plugins.indent_convert.update_indent_type then
    doc.indent_info = {
      type = "soft",
      size = indent_size,
      confirmed = true
    }
  end
end

local function spaces_to_tabs()
  local doc = core.active_view.doc
  local indent_size = get_indent_size(doc)
  local selections = doc.selections
  doc:replace(replacer(doc, spaces_replacer, indent_size))
  doc.selections = selections
  doc:sanitize_selection()
  if config.plugins.indent_convert.update_indent_type then
    doc.indent_info = {
      type = "hard",
      size = indent_size,
      confirmed = true
    }
  end
end

command.add("core.docview", {
    ["indent-convert:tabs-to-spaces"] = tabs_to_spaces,
    ["indent-convert:spaces-to-tabs"] = spaces_to_tabs
  }
)