clp

command line syntax highlighter
git clone git://jeskin.net/clp.git
README | Log | Files | Refs | LICENSE

commit db23bd8ff8a47b0cd0691f00e44a013afe6f5586
parent a45f43efbc6fc789ad9282e66f477ddafe9541e5
Author: Jon Eskin <eskinjp@gmail.com>
Date:   Sun, 17 Jul 2022 11:50:11 -0400

Add highlight-line option

Diffstat:
Mclp.c | 17+++++++++++------
Mlua/clp.lua | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mlua/colors.lua | 6++++++
Mlua/theme.lua | 27+++++++++++++++++++++++++++
4 files changed, 131 insertions(+), 12 deletions(-)

diff --git a/clp.c b/clp.c @@ -86,6 +86,7 @@ static bool package_exists(lua_State *L, const char *name) { int main(int argc, char *argv[]) { struct optparse_long longopts[] = { + {"highlight_line", 'h', OPTPARSE_REQUIRED}, {"override-filetype", 't', OPTPARSE_REQUIRED}, {"list-overrides", 'l', OPTPARSE_NONE}, {0}}; @@ -137,6 +138,7 @@ int main(int argc, char *argv[]) { int option = 0; char *filename = ""; char *filetype_override = ""; + int highlight_line = 0; struct optparse options; optparse_init(&options, argv); @@ -153,16 +155,19 @@ int main(int argc, char *argv[]) { return 0; case 't': filetype_override = options.optarg; + lua_pushliteral(L, "filetype_override"); + lua_pushstring(L, filetype_override); + lua_settable(L, -3); + break; + case 'h': + highlight_line = atoi(options.optarg); + lua_pushliteral(L, "highlight_line"); + lua_pushinteger(L, highlight_line); + lua_settable(L, -3); break; } } - if(!(strlen(filetype_override) == 0)) { - lua_pushliteral(L, "filetype_override"); - lua_pushstring(L, filetype_override); - lua_settable(L, -3); - } - filename = optparse_arg(&options); if (!filename) { diff --git a/lua/clp.lua b/lua/clp.lua @@ -1,29 +1,42 @@ clp = {} local theme = require('theme') +local colors = require('colors') local ftdetect = require('ftdetect') local lexers = require('lexer') local default_theme = theme.default_theme +local highlight_theme = theme.highlight_theme --- https://github.com/martanne/vis/issues/601#issuecomment-327018674 function write(args) local filename = args.filename - local filetype_override = args.filetype_override + local highlight_line = args.highlight_line + local file = assert(io.open(filename, 'r')) + local text = file:read('*all') local syntax if filetype_override ~= nil then syntax = filetype_override else syntax = ftdetect.lookup_lexer(filename) end - local lexer = lexers.load(syntax) if not lexer then print(string.format('Failed to load lexer: `%s`', syntax)) return 1 end + local hl_line_start, hl_line_end + if (highlight_line ~= nil) then + hl_start_pos, hl_end_pos = find_endl(text, highlight_line) + end + if (hl_start_pos ~= nil) then + write_hl(text, lexer, hl_start_pos, hl_end_pos) + else + write_nohl(text, lexer) + end + file:close() +end - local file = assert(io.open(filename, 'r')) - local text = file:read('*all') +-- https://github.com/martanne/vis/issues/601#issuecomment-327018674 +function write_nohl(text, lexer) local tokens = lexer:lex(text, 1) local token_start = 1 local last = '' @@ -47,7 +60,75 @@ function write(args) io.write(text:sub(token_start, token_end)) token_start = token_end + 1 end - file:close() +end + +-- find nth and n+1th indices of the endline character in a string +function find_endl(s, n) + local i = 0 + local hl_start_pos + if n == 1 then + return 0,string.find(s,"\n", i + 1) + end + while true do + i = string.find(s, "\n", i + 1) + if i == nil then + return nil + end + if n == 2 then + hl_start_pos = i + break + end + n = n - 1 + end + i = string.find(s, "\n", i + 1) + if i == nil then + return i, nil + else + return hl_start_pos, i + end + return nil +end + +function write_hl(text, lexer, hl_line_start, hl_line_end) + -- gsub the text before, during... can we just say "rest"? + -- then lets print as normal and see if there's a performance hit + -- check if hl_line_end is nil + -- substring from 0 to hl_line_start + local pre_hl = text:sub(0, hl_line_start) + local hl = text:sub(hl_line_start, hl_line_end) + -- substitute newlines for blanks in hl + hl = hl:gsub("\n", "") + local post_hl = text:sub(hl_line_end, nil) + write_thing(pre_hl, lexer, default_theme) + if (hl ~= nil) then write_thing(hl, lexer, highlight_theme) end + colors.reset_colors() + if (post_hl ~= nil) then write_thing(post_hl, lexer, default_theme) end +end + +function write_thing(text, lexer, style) + local tokens = lexer:lex(text, 1) + local token_start = 1 + local last = '' + + for i = 1, #tokens, 2 do + local token_end = tokens[i + 1] - 1 + local name = tokens[i] + + local current_style = style[name] + if current_style ~= nil then + -- Whereas the lexer reports all other syntaxes over + -- the entire span of a token, it reports 'default' + -- byte-by-byte. We emit only the first 'default' of + -- a series in order to properly display multibyte + -- UTF-8 characters. + if not (last == 'default' and name == 'default') then + io.write(tostring(current_style)) + end + last = name + end + io.write(text:sub(token_start, token_end)) + token_start = token_end + 1 + end end function print_available_overrides() diff --git a/lua/colors.lua b/lua/colors.lua @@ -39,6 +39,10 @@ function colormt:__call(s) return self .. s .. colors.reset end +function reset_colors() + io.write(tostring(colors.reset)) +end + local function makecolor(value) return setmetatable({ value = schar(27) .. '[' .. tostring(value) .. 'm' }, colormt) end @@ -80,4 +84,6 @@ for c, v in pairs(colorvalues) do colors[c] = makecolor(v) end +colors.reset_colors = reset_colors + return colors diff --git a/lua/theme.lua b/lua/theme.lua @@ -28,7 +28,34 @@ local default_theme = { ['identifier'] = colors.white, } +local highlight_theme = { + -- bold => bright + -- italics => underscore + ['default'] = colors.white .. colors.reverse, + ['nothing'] = '' .. colors.reverse, + ['class'] = colors.yellow .. colors.bright .. colors.reverse, + ['comment'] = colors.blue .. colors.bright .. colors.reverse, + ['constant'] = colors.cyan .. colors.bright .. colors.reverse, + ['definition'] = colors.blue .. colors.bright .. colors.reverse, + ['error'] = colors.red .. colors.underscore .. colors.reverse, + ['function'] = colors.blue .. colors.bright .. colors.reverse, + ['keyword'] = colors.yellow .. colors.bright .. colors.reverse, + ['label'] = colors.green .. colors.bright .. colors.reverse, + ['number'] = colors.red .. colors.bright .. colors.reverse, + ['operator'] = colors.cyan .. colors.bright .. colors.reverse, + ['regex'] = colors.green .. colors.bright .. colors.reverse, + ['string'] = colors.red .. colors.bright .. colors.reverse, + ['preprocessor'] = colors.magenta .. colors.bright .. colors.reverse, + ['tag'] = colors.red .. colors.bright .. colors.reverse, + ['type'] = colors.green .. colors.bright .. colors.reverse, + ['variable'] = colors.blue .. colors.bright .. colors.reverse, + ['whitespace'] = '' .. colors.reverse, + ['embedded'] = colors.onblue .. colors.bright .. colors.reverse, + ['identifier'] = colors.white .. colors.reverse, +} + theme['default_theme'] = default_theme +theme['highlight_theme'] = highlight_theme theme['colors'] = colors return theme