diff --git a/Makefile b/Makefile index f9e8f06..149de5c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ set_lua_paths = eval $$(luarocks path --lua-version 5.1 --bin) busted = $$(find /usr/local/lib/luarocks/*/busted/* -name busted) +set_luals_path = PATH="$$PATH:/home/yaro/.local/share/nvim/mason/bin/lua-language-server" test_unit = busted --run=unit test_nvim = nvim --headless -i NONE -n -u spec/minimal_init.lua -l $(busted) --run=unit @@ -15,7 +16,7 @@ api_documentation: nvim -u scripts/make_api_documentation/init.lua -l scripts/make_api_documentation/main.lua llscheck: - llscheck --configpath .luarc.json . + @$(set_luals_path) && llscheck --configpath .luarc.json . luacheck: luacheck lua spec scripts diff --git a/README.md b/README.md index fbd8585..3d5fb36 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ **lua-console.nvim** - is a handy scratch pad / REPL / debug console for Lua development and Neovim exploration and configuration. Acts as a user friendly replacement of command mode - messages loop and as a handy scratch pad to store and test your code gists. -***Update: Although it originated as a tool for Lua development, it has now evolved into supporting other languages too. See [`evaluating other languages`](#evaluating-other-languages).*** + +***Update: although it originated as a tool for Lua development, it has now evolved into supporting other languages too. See [`evaluating other languages`](#evaluating-other-languages).***
@@ -52,6 +53,7 @@ config. If you want to delete a mapping - set its value to `false`. `config.lua` + ```lua opts = { buffer = { @@ -139,10 +141,10 @@ There are two functions available within the console: #### Setting up -- It is possible to setup external code executors for other languages. Evaluators for `ruby` and `racket` are working out of the box, support for other languages is coming. +- It is possible to setup external code executors for other languages. Evaluators for `ruby`,`racket` and `python` are working out of the box, support for other languages is coming. Meanwhile, you can easily setup your own language. -- Below is the default configuration which can be overridden or extended by your custom config (`default_process_opts` will be - replaced by language specific opts), e.g. a possible config for `python` could be: +- Below is the default configuration, which can be overridden or extended by your custom config, where `default_process_opts` will be + replaced by language specific opts, e.g. a possible config for `python` could be: ```lua require('lua-console').setup { @@ -157,12 +159,11 @@ There are two functions available within the console: } ``` -- You can also setup a custom formatter to format the evaluator output before appending results to the console or buffer. Example is in the config. -
Default External Evaluators Settings `exev_config.lua` + ```lua ---Formats the output of external evaluator ---@param result string[] @@ -209,23 +210,24 @@ There are two functions available within the console: return external_evaluators ``` -
+- You can also setup a custom formatter to format the evaluator output before appending results to the console or buffer. Example is in the config. + + #### Usage - The language evaluator is determined either from (in order of precedence): - - The code prefix `===lang` on the line above your code snippet, in which case it only applies to the snippet directly below and it should be included in the selection - for evaluation. The prefix can be changed in the config. + - The code prefix `===lang` on the line above your code snippet, in which case it only applies to the snippet directly below. The prefix can be changed in the config. - The code prefix on the top line of the console/buffer, in which case it applies to the whole buffer. - The file type of the buffer. +
```racket ===racket - (define (log str) (displayln (format "~v" str))) @@ -243,6 +245,18 @@ There are two functions available within the console: 5.times { puts 'Hey' } ``` +- Code inside Lua comments will be sytax highlighted. + + + ```python + [[===python + list = [1, 3, 5, 7, 9] + + for val in a: + print(list) + ]] + ``` + ## Alternatives and comparison There are a number of alternatives available, notably: diff --git a/lua/lua-console.lua b/lua/lua-console.lua index 47be54d..9cda634 100644 --- a/lua/lua-console.lua +++ b/lua/lua-console.lua @@ -1,4 +1,4 @@ -local config, mappings, utils +local config, mappings, utils, injections local get_or_create_buffer = function() --- @type number @@ -19,6 +19,7 @@ local get_or_create_buffer = function() vim.api.nvim_buf_set_name(buf, buf_name) -- the name is only needed so the buffer is picked up by Lsp with correct root + injections.set_highlighting() vim.api.nvim_set_option_value('filetype', 'lua', { buf = buf }) vim.diagnostic.enable(false, { bufnr = buf }) @@ -77,6 +78,7 @@ local setup = function(opts) config = require('lua-console.config').setup(opts) mappings = require('lua-console.mappings') utils = require('lua-console.utils') + injections = require('lua-console.injections') mappings.set_global_mappings() mappings.set_console_commands() diff --git a/lua/lua-console/injections.lua b/lua/lua-console/injections.lua new file mode 100644 index 0000000..9c9b478 --- /dev/null +++ b/lua/lua-console/injections.lua @@ -0,0 +1,29 @@ +local M = {} + +---Allow syntax highlighting for languages embedded in Lua comments +M.set_highlighting = function() + local config = require('lua-console.config') + local lang_prefix = config.external_evaluators.lang_prefix + local lang_pattern = ('^%s([^\\n]-)\\n.+$'):format(lang_prefix) + + vim.treesitter.query.add_directive('indent!', function(_, _, _, predicate, metadata) + local capture_id = predicate[2] + if not metadata[capture_id].range then return end + + metadata[capture_id].range[2] = tonumber(predicate[3]) -- set indent col to 0 + end, { all = true, force = true }) + + local query = ([[ ;query + ; extends + (string + content: (string_content) @injection.language @injection.content + (#lua-match? @injection.language "^@1") + (#gsub! @injection.language "@2" "%1") + (#offset! @injection.content 1 0 0 0) + (#indent! @injection.content 0)) + ]]):gsub('@1', lang_prefix):gsub('@2', lang_pattern) + + vim.treesitter.query.set('lua', 'injections', query) +end + +return M diff --git a/lua/lua-console/utils.lua b/lua/lua-console/utils.lua index 27acc27..8d44217 100644 --- a/lua/lua-console/utils.lua +++ b/lua/lua-console/utils.lua @@ -12,6 +12,7 @@ local to_string = function(tbl, sep, trim) for _, pat in ipairs(patterns) do line = line:gsub(pat, '') end + -- compact strings by removing redundant spaces line = line:gsub('(["\'])%s+', '%1'):gsub('%s+(["\'])', '%1'):gsub('%s%s+', ' ') end @@ -22,6 +23,13 @@ local to_table = function(str) return vim.split(str or '', '\n', { trimempty = true }) end +local function remove_indentation(tbl) + local indent = tbl[1]:match('(%s*)%w') or tbl[1]:match('(\t*)%w') + return vim.tbl_map(function(line) + return line:sub(#indent + 1) + end, tbl) +end + ---Shows virtual text in the buffer ---@param buf number buffer ---@param id number namespace id @@ -60,9 +68,9 @@ local toggle_help = function(buf) vim.api.nvim_buf_del_extmark(buf, ns, 1) message = - [[%s - eval a line or selection, %s - open file, %s - load messages, %s - save console, %s - load console, %s/%s - resize window, %s - toggle help]] + [[%s - eval a line or selection, %s - eval buffer, %s - open file, %s - load messages, %s - save console, %s - load console, %s/%s - resize window, %s - toggle help]] message = - string.format(message, cm.eval, cm.open, cm.messages, cm.save, cm.load, cm.resize_up, cm.resize_down, cm.help) + string.format(message, cm.eval, cm.eval_buffer, cm.open, cm.messages, cm.save, cm.load, cm.resize_up, cm.resize_down, cm.help) local visible_line = vim.fn.line('w0') show_virtual_text(buf, 2, message, visible_line - 1, 'overlay', 'Comment') @@ -131,7 +139,7 @@ local print_buffer = {} local append_current_buffer = function(buf, lines) if not lines or #lines == 0 then return end - local lnum = vim.fn.line('.', vim.fn.bufwinid(buf)) + local lnum = vim.fn.line('.') local prefix = config.buffer.result_prefix local virtual_text @@ -331,7 +339,7 @@ local get_external_evaluator = function(buf, lang) return function(lines) local cmd = vim.tbl_extend('force', {}, lang_config.cmd) - local code = (lang_config.code_prefix or '') .. to_string(lines) + local code = (lang_config.code_prefix or '') .. to_string(remove_indentation(lines)) -- some languages, like python are concerned with indentation table.insert(cmd, code) local status, id = pcall(vim.system, cmd, opts, opts.on_exit) @@ -346,42 +354,33 @@ end ---Determines the language of the code/console/buffer ---mutates lines array to remove the lang_prefix ---@param buf number ----@param lines string[] +---@param range number[] ---@return string -local function get_lang(buf, lines) - local pattern = '^.*' .. config.external_evaluators.lang_prefix .. '(.-)%s*$' +local function get_lang(buf, range) + local pattern = ('^.*' .. config.external_evaluators.lang_prefix .. '(.-)%s*$') local line, lang - line = lines[1] + line = vim.api.nvim_buf_get_lines(buf, math.max(0, range[1] - 2), range[2], false)[1] lang = line:match(pattern) - if lang then - table.remove(lines, 1) - return lang - end + if lang then return lang end - line = vim.fn.getbufline(buf, 1)[1] + line = vim.api.nvim_buf_get_lines(buf, 0, 1, false)[1] lang = line:match(pattern) if lang then return lang end return vim.bo[buf].filetype end -local get_evaluator = function(buf, lines) - local evaluator, lang - lang = get_lang(buf, lines) +local get_evaluator = function(buf, range) + local lang = get_lang(buf, range) if lang == '' then vim.notify('Plese specify the language to evaluate or set the filetype', vim.log.levels.WARN) - return - end - - if lang == 'lua' then - evaluator = lua_evaluator + elseif lang == 'lua' then + return lua_evaluator else - evaluator = get_external_evaluator(buf, lang) + return get_external_evaluator(buf, lang) end - - return evaluator end ---Evaluates code in the current line or visual selection and appends to buffer @@ -409,7 +408,7 @@ local eval_code_in_buffer = function(buf, full) lines = remove_empty_lines(lines) if #lines == 0 then return end - local evaluator = get_evaluator(buf, lines) + local evaluator = get_evaluator(buf, { v_start, v_end }) if not evaluator then return end local result = evaluator(lines) diff --git a/spec/spec_helper.lua b/spec/spec_helper.lua index e04a472..b06b7e6 100644 --- a/spec/spec_helper.lua +++ b/spec/spec_helper.lua @@ -155,6 +155,7 @@ local function compare_strings(str_1, str_2) if char_1 ~= char_2 then break end end + if not pos then return '' end pos = pos + 1 local sub_1 = str_1:sub(pos - 5, pos - 1) .. '<< ' .. str_1:sub(pos, pos) .. ' >>' .. str_1:sub(pos + 1, pos + 5) diff --git a/spec/unit/external_evals_spec.lua b/spec/unit/external_evals_spec.lua index b4b8ad1..883653c 100644 --- a/spec/unit/external_evals_spec.lua +++ b/spec/unit/external_evals_spec.lua @@ -80,7 +80,7 @@ describe('external evaluators', function() h.set_buffer(buf, content) - h.send_keys('4gg') + h.send_keys('5gg') h.send_keys('Vj') utils.eval_code_in_buffer() @@ -232,6 +232,37 @@ describe('external evaluators', function() end) end) + it('uses removes indentation from code', function() + config.setup { + external_evaluators = { ruby = { code_prefix = '' }, }, } + + content = { + ' a = [1, 3, 5, 7, 9]', + ' for val in a:', + ' print(val)' + } + + expected = { + 'a = [1, 3, 5, 7, 9]', + ' for val in a:', + ' print(val)' + } + + h.set_buffer(buf, content) + + h.send_keys('VG') + utils.eval_code_in_buffer() + + assert.stub(vim.system).was.called_with( + match.assert_arg(function(arg) + result = h.to_table(arg[3]) + assert.is_same(result, expected) + end), + _, + _ + ) + end) + it('uses custom formatter to process results', function() vim.system = vim_system vim.g._wait_for_spec = false diff --git a/spec/unit/lua-console_spec.lua b/spec/unit/lua-console_spec.lua index 9385f3f..67db4d1 100644 --- a/spec/unit/lua-console_spec.lua +++ b/spec/unit/lua-console_spec.lua @@ -23,7 +23,7 @@ describe('lua-console.nvim', function() it('sets up with custom config', function() config = { buffer = { - result_prefix = '$$ ', + result_prefix = '$$ ' }, window = { border = 'single',