aboutsummaryrefslogtreecommitdiff
path: root/plugins/indent_convert.lua
blob: 45d8640ba21d747cbe13ff20509a7840936e9ed1 (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
-- mod-version:3
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({
  -- set to false to avoid updating the document indent type
  update_indent_type = true,
  -- The config specification used by the settings gui
  config_spec = {
    name = "Indent Convert",
    {
      label = "Update Indent Type",
      description = "Disable to avoid updating the document indent type.",
      path = "update_indent_type",
      type = "toggle",
      default = true
    }
  }
}, 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(dv)
  local doc = dv.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(dv)
  local doc = dv.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
  }
)