171 lines
4.3 KiB
Lua
171 lines
4.3 KiB
Lua
VERSION = "0.1.0"
|
|
PLUGIN_NAME = "flow"
|
|
|
|
local micro = import("micro")
|
|
local config = import("micro/config")
|
|
local util = import("micro/util")
|
|
local buffer = import("micro/buffer")
|
|
|
|
function init()
|
|
config.MakeCommand(PLUGIN_NAME, flowIndent, config.NoComplete)
|
|
config.MakeCommand(PLUGIN_NAME .. ".left", flowLeft, config.NoComplete)
|
|
config.MakeCommand(PLUGIN_NAME .. ".right", flowRight, config.NoComplete)
|
|
config.MakeCommand(PLUGIN_NAME .. ".center", flowCenter, config.NoComplete)
|
|
-- config.MakeCommand(PLUGIN_NAME .. ".justify", flowJustify, config.NoComplete)
|
|
end
|
|
|
|
function flowIndent(bufferPane)
|
|
return flow(bufferPane, "indent")
|
|
end
|
|
|
|
function flowLeft(bufferPane)
|
|
return flow(bufferPane, "left")
|
|
end
|
|
|
|
function flowRight(bufferPane)
|
|
return flow(bufferPane, "right")
|
|
end
|
|
|
|
function flowCenter(bufferPane)
|
|
return flow(bufferPane, "center")
|
|
end
|
|
|
|
function flowJustify(bufferPane)
|
|
return flow(bufferPane, "justify")
|
|
end
|
|
|
|
function flow(bufferPane, align)
|
|
local cursor = bufferPane.Buf:GetActiveCursor()
|
|
if not (cursor and cursor:HasSelection()) then return end
|
|
|
|
-- TODO: if there is no selection, operate on the start and end of
|
|
-- the currently selected paragraph, which is demarcated by double line
|
|
-- breaks/beginning/end of file
|
|
|
|
local sstart, send = nil, nil
|
|
if cursor.CurSelection[1]:GreaterThan(-cursor.CurSelection[2]) then
|
|
sstart, send = cursor.CurSelection[2], cursor.CurSelection[1]
|
|
else
|
|
sstart, send = cursor.CurSelection[1], cursor.CurSelection[2]
|
|
end
|
|
sstart = buffer.Loc(sstart.X, sstart.Y)
|
|
send = buffer.Loc(send.X, send.Y)
|
|
|
|
local selection = util.String(cursor:GetSelection())
|
|
bufferPane.Buf:Replace(sstart, send, reflow(selection, align, colorColumn()))
|
|
end
|
|
|
|
function reflow(text, alignment, columns)
|
|
local trailingBreak = text:sub(#text, #text) == "\n"
|
|
|
|
-- get indentation information
|
|
local firstLineIndent, middleLineIndent, inputLines = splitIndentation(text)
|
|
firstLineIndent = firstLineIndent or ""
|
|
middleLineIndent = middleLineIndent or ""
|
|
|
|
-- tokenize
|
|
local tokens = { }
|
|
for _, line in ipairs(inputLines) do
|
|
tokens = append(tokens, tokenize(line))
|
|
end
|
|
|
|
-- format tokens
|
|
local result = ""
|
|
local line = ""
|
|
local currentColumns = columns - measure(firstLineIndent)
|
|
function addLine()
|
|
local first = false
|
|
if result == "" then
|
|
first = true
|
|
else
|
|
result = result .. "\n"
|
|
end
|
|
if alignment == "indent" then
|
|
if first then
|
|
line = firstLineIndent .. line
|
|
else
|
|
line = middleLineIndent .. line
|
|
end
|
|
else
|
|
line = align(line, alignment, columns)
|
|
end
|
|
result = result .. line
|
|
currentColumns = columns - measure(middleLineIndent)
|
|
end
|
|
for _, token in ipairs(tokens) do
|
|
local forecast = #line + 1 + #token
|
|
if forecast > currentColumns then
|
|
addLine()
|
|
line = ""
|
|
end
|
|
if line ~= "" then line = line .. " " end
|
|
line = line .. token
|
|
end
|
|
addLine()
|
|
|
|
if trailingBreak then result = result .. "\n" end
|
|
return result
|
|
end
|
|
|
|
-- assumes the text begins directly at the start of the file or directly after
|
|
-- a line break. returns the indentation for the first line, lines in the
|
|
-- middle, and the last line.
|
|
function splitIndentation(text)
|
|
local indents = { }
|
|
local lines = { }
|
|
for line in text:gmatch("([^\n]*)\n?") do
|
|
local indent = line:match("^[ \t/#*-]+") or ""
|
|
table.insert(indents, indent)
|
|
table.insert(lines, line:sub(#indent + 1))
|
|
end
|
|
if #indents < 1 then
|
|
return "", "", "", lines
|
|
end
|
|
local firstLineIndent = indents[1] or ""
|
|
local middleLineIndent = indents[2] or ""
|
|
return firstLineIndent, middleLineIndent, lines
|
|
end
|
|
|
|
function align(line, alignment, columns)
|
|
local gap = columns - #line
|
|
if alignment == "right" then
|
|
return string.rep(" ", gap) .. line
|
|
elseif alignment == "center" then
|
|
return string.rep(" ", math.floor(gap / 2)) .. line
|
|
else
|
|
return line
|
|
end
|
|
end
|
|
|
|
function tokenize(text)
|
|
local tokens = { }
|
|
for token in text:gmatch("%S+") do
|
|
table.insert(tokens, token)
|
|
end
|
|
return tokens
|
|
end
|
|
|
|
function measure(text)
|
|
if not text then return 0 end
|
|
local width = 0
|
|
for index = 1, #text do
|
|
if text:sub(index, index) == "\t" then
|
|
width = math.ceil((width + 1) / 8) * 8
|
|
else
|
|
width = width + 1
|
|
end
|
|
end
|
|
return width
|
|
end
|
|
|
|
function append(dest, src)
|
|
for index = 1, #src do
|
|
dest[#dest + 1] = src[index]
|
|
end
|
|
return dest
|
|
end
|
|
|
|
function colorColumn()
|
|
return config.GetGlobalOption("colorcolumn")
|
|
end
|