From f23876f07babaa56753d0adb4fc1c5f79abf5434 Mon Sep 17 00:00:00 2001 From: "sashakoshka@tebibyte.media" Date: Thu, 9 Jan 2025 21:41:43 -0500 Subject: [PATCH] flow: Add text reflow plugin --- flow/go.lua | 168 +++++++++++++++++++++++++++++++++++++++++++++++++ flow/repo.json | 5 ++ 2 files changed, 173 insertions(+) create mode 100644 flow/go.lua create mode 100644 flow/repo.json diff --git a/flow/go.lua b/flow/go.lua new file mode 100644 index 0000000..862cbf1 --- /dev/null +++ b/flow/go.lua @@ -0,0 +1,168 @@ +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()) + selection = reflow(selection, align, colorColumn()) + bufferPane.Buf:Replace(sstart, send, selection) +end + +function reflow(text, alignment, columns) + -- 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() + + 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 diff --git a/flow/repo.json b/flow/repo.json new file mode 100644 index 0000000..e367562 --- /dev/null +++ b/flow/repo.json @@ -0,0 +1,5 @@ +[{ + "Name": "case", + "Description": "Change the case of the selection", + "License": "GPLv3", +}]