diff --git a/README.md b/README.md index 9ebe147..3a310c2 100644 --- a/README.md +++ b/README.md @@ -190,27 +190,58 @@ require('cmp').setup({ `fittencode.nvim` provides a set of APIs to help you integrate it with other plugins or scripts. -| API | Description | -|--------------------------|----------------------------------------| -| `login` | Login to Fitten | -| `logout` | Logout from Fitten | -| `register` | Register to Fitten | -| `set_log_level` | Set the log level | -| `get_current_status` | Get the current status of the `Engine` | -| `has_suggestion` | Check if there is a suggestion | -| `accept_all_suggestions` | Accept all suggestions | -| `accept_line` | Accept line | -| `accept_word` | Accept word | -| `document_code` | Document code | -| `edit_code` | Edit code | -| `explain_code` | Explain code | -| `find_bugs` | Find bugs | -| `generate_unit_test` | Generate unit test | -| `implement_features` | Implement features | -| `improve_code` | Improve code | -| `refactor_code` | Refactor code | -| `start_chat` | Start chat | -| `stop_eval` | Stop the evaluation | +- Access the APIs by calling `require('fittencode').()`. + +### Parameters Types + +```lua +-- Log levels +vim.log = { + levels = { + TRACE = 0, + DEBUG = 1, + INFO = 2, + WARN = 3, + ERROR = 4, + OFF = 5, + }, +} + +---@class ActionOptions +---@field prompt? string +---@field content? string +---@field language? string + +---@class GenerateUnitTestOptions : ActionOptions +---@field test_framework string + +---@class ImplementFeaturesOptions : ActionOptions +---@field feature_type string +``` + +### List of APIs + +| API | Prototype | Description | +|--------------------------|------------------------------------------------|-----------------------------------------------------------------| +| `login` | `login(username, password)` | Login to Fitten Code AI | +| `logout` | `logout()` | Logout from Fitten Code AI | +| `register` | `register()` | Register to Fitten Code AI | +| `set_log_level` | `set_log_level(level)` | Set the log level | +| `get_current_status` | `get_current_status()` | Get the current status of the `InlineEngine` and `ActionEngine` | +| `has_suggestion` | `has_suggestion()` | Check if there is a suggestion | +| `accept_all_suggestions` | `accept_all_suggestions()` | Accept all suggestions | +| `accept_line` | `accept_line()` | Accept line | +| `accept_word` | `accept_word()` | Accept word | +| `document_code` | `document_code(ActionOptions)` | Document code | +| `edit_code` | `edit_code(ActionOptions)` | Edit code | +| `explain_code` | `explain_code(ActionOptions)` | Explain code | +| `find_bugs` | `find_bugs(ActionOptions)` | Find bugs | +| `generate_unit_test` | `generate_unit_test(GenerateUnitTestOptions)` | Generate unit test | +| `implement_features` | `implement_features(ImplementFeaturesOptions)` | Implement features | +| `improve_code` | `improve_code(ActionOptions)` | Improve code | +| `refactor_code` | `refactor_code(ActionOptions)` | Refactor code | +| `start_chat` | `start_chat(ActionOptions)` | Start chat | +| `stop_eval` | `stop_eval()` | Stop the evaluation | ## 🎉 Special Thanks diff --git a/lua/fittencode/engines/actions.lua b/lua/fittencode/engines/actions.lua index 2c72a88..f2425db 100644 --- a/lua/fittencode/engines/actions.lua +++ b/lua/fittencode/engines/actions.lua @@ -180,10 +180,12 @@ local function find_nospace(line) end ---@param buffer number ----@param start_row number ----@param end_row number +---@param range ActionRange ---@return string[] -local function get_tslangs(buffer, start_row, end_row) +local function get_tslangs(buffer, range) + local start_row = range.start[1] - 1 + local end_row = range['end'][1] - 1 + local row = start_row local col = 0 @@ -208,7 +210,38 @@ local function get_tslangs(buffer, start_row, end_row) return langs end -local vmode = { ['v']=true, ['V']=true, ['']=true } +---@class ActionRange +---@field start integer[] +---@field end integer[] +---@field vmode boolean +---@field region string[] + +local VMODE = { ['v'] = true, ['V'] = true, [api.nvim_replace_termcodes('', true, true, true)] = true } + +local function make_range(buffer) + local in_v = false + local region = {} + + local mode = api.nvim_get_mode().mode + Log.debug('Action mode: {}', mode) + if VMODE[mode] then + in_v = true + region = fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() }) + end + + api.nvim_feedkeys(api.nvim_replace_termcodes('', true, true, true), 'nx', false) + + local start = api.nvim_buf_get_mark(buffer, '<') + local end_ = api.nvim_buf_get_mark(buffer, '>') + + local range = { + start = start, + ['end'] = end_, + vmode = in_v, + region = region, + } + return range +end ---@param action number ---@param opts? ActionOptions @@ -237,21 +270,16 @@ function ActionsEngine.start_action(action, opts) local window = api.nvim_get_current_win() local buffer = api.nvim_win_get_buf(window) - local sln, eln = api.nvim_buf_get_mark(buffer, '<')[1], api.nvim_buf_get_mark(buffer, '>')[1] - Log.debug('mode: {}', api.nvim_get_mode().mode) - if vmode[api.nvim_get_mode().mode] then - api.nvim_feedkeys('', 'nx', false) - end - sln, eln = api.nvim_buf_get_mark(buffer, '<')[1], api.nvim_buf_get_mark(buffer, '>')[1] - Log.debug('sln: {}, eln: {}', sln, eln) + local range = make_range(buffer) + Log.debug('Action range: {}', range) chat:show() fn.win_gotoid(window) local filetype = api.nvim_get_option_value('filetype', { buf = buffer }) Log.debug('Action filetype: {}', filetype) - local langs = get_tslangs(buffer, sln - 1, eln - 1) + local langs = get_tslangs(buffer, range) Log.debug('Action langs: {}', langs) if filetype == 'markdown' and #langs >= 2 then filetype = vim.tbl_filter(function(lang) return lang ~= 'markdown' end, langs)[1] @@ -261,7 +289,7 @@ function ActionsEngine.start_action(action, opts) local prompt_opts = { window = window, buffer = buffer, - range = { sln - 1, eln - 1 }, + range = range, filetype = filetype, prompt_ty = get_action_type(action), solved_content = opts and opts.content, @@ -273,7 +301,7 @@ function ActionsEngine.start_action(action, opts) if #prompt_preview.filename == 0 then prompt_preview.filename = 'unnamed' end - local source_info = ' (' .. prompt_preview.filename .. ' ' .. sln .. ':' .. eln .. ')' + local source_info = ' (' .. prompt_preview.filename .. ' ' .. range.start[1] .. ':' .. range['end'][1] .. ')' local c_in = '# In`[' .. current_eval .. ']`:= ' .. action_name .. source_info chat:commit(c_in) diff --git a/lua/fittencode/prompt_providers/actions.lua b/lua/fittencode/prompt_providers/actions.lua index 7148356..107235f 100644 --- a/lua/fittencode/prompt_providers/actions.lua +++ b/lua/fittencode/prompt_providers/actions.lua @@ -28,6 +28,34 @@ function M:get_priority() return self.priority end +local function max_len(buffer, row, len) + local max = string.len(api.nvim_buf_get_lines(buffer, row - 1, row, false)[1]) + if len > max then + return max + end + return len +end + +---@param buffer integer +---@param range ActionRange +---@return string +local function get_range_content(buffer, range) + local lines = {} + if range.vmode then + lines = range.region + else + -- lines = api.nvim_buf_get_text(buffer, range.start[1] - 1, 0, range.start[1] - 1, -1, {}) + local end_col = max_len(buffer, range['end'][1], range['end'][2]) + lines = api.nvim_buf_get_text( + buffer, + range.start[1] - 1, + range.start[2], + range['end'][1] - 1, + end_col + 1, {}) + end + return table.concat(lines, '\n') +end + ---@param ctx PromptContext ---@return Prompt? function M:execute(ctx) @@ -47,15 +75,10 @@ function M:execute(ctx) if ctx.solved_prefix then prefix = ctx.solved_prefix else - -- FIXME: Improve prompt construction! full content with line:col info? - ---@diagnostic disable-next-line: param-type-mismatch if ctx.solved_content then content = ctx.solved_content else - -- if ctx.range[1] == ctx.range[2] then - -- content = api.nvim_buf_get_lines(ctx.buffer, ctx.range[1], ctx.range[1] + 1, false)[1] - -- content = table.concat(api.nvim_buf_get_text(ctx.buffer, 0, 0, -1, -1, {}), '\n') - content = table.concat(api.nvim_buf_get_text(ctx.buffer, ctx.range[1], 0, ctx.range[2], -1, {}), '\n') + content = get_range_content(ctx.buffer, ctx.range) end local name = ctx.prompt_ty:sub(#NAME + 2) Log.debug('Action Name: {}', name) diff --git a/lua/fittencode/prompt_providers/init.lua b/lua/fittencode/prompt_providers/init.lua index fb93b81..f101ba7 100644 --- a/lua/fittencode/prompt_providers/init.lua +++ b/lua/fittencode/prompt_providers/init.lua @@ -21,7 +21,7 @@ local M = {} ---@field prompt_ty? string ---@field row? integer ---@field col? integer ----@field range? table +---@field range? ActionRange ---@field prompt? string ---@field solved_prefix? string ---@field solved_content? string diff --git a/lua/fittencode/views/chat.lua b/lua/fittencode/views/chat.lua index 150f57a..3fa8f2d 100644 --- a/lua/fittencode/views/chat.lua +++ b/lua/fittencode/views/chat.lua @@ -60,7 +60,9 @@ function M:close() if self.win == nil then return end - api.nvim_win_close(self.win, true) + if api.nvim_win_is_valid(self.win) then + api.nvim_win_close(self.win, true) + end self.win = nil -- api.nvim_buf_delete(self.buffer, { force = true }) -- self.buffer = nil @@ -92,7 +94,10 @@ function M:commit(text, linebreak) api.nvim_set_option_value('modifiable', false, { buf = self.buffer }) end table.move(lines, 1, #lines, #self.text + 1, self.text) - api.nvim_win_set_cursor(self.win, { #self.text, 0 }) + + if api.nvim_win_is_valid(self.win) then + api.nvim_win_set_cursor(self.win, { #self.text, 0 }) + end end local function _sub_match(s, pattern)