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