Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce GuessProgrammingLanguage & AnalyzeData #63

Merged
merged 16 commits into from
May 19, 2024
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,21 @@ require('cmp').setup({
| `Fitten logout` | Logout account |

### Action Commands

| Command | Description |
|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Fitten document_code` | Document code |
| `Fitten edit_code` | Edit code |
| `Fitten explain_code` | Explain code |
| `Fitten find_bugs` | Find bugs |
| `Fitten generate_unit_test` | Generate unit test. Try the command `Fitten generate_unit_test <test_framework> <language>` to generate unit test with specific test framework and language. |
| `Fitten implement_features` | Implement features |
| `Fitten improve_code` | Improve code |
| `Fitten refactor_code` | Refactor code |
| `Fitten start_chat` | Start chat |
- Try the command `Fitten generate_unit_test <test_framework> <language>` to generate unit test with specific test framework and language.

| Command | Description |
|-------------------------------------|----------------------------|
| `Fitten document_code` | Document code |
| `Fitten edit_code` | Edit code |
| `Fitten explain_code` | Explain code |
| `Fitten find_bugs` | Find bugs |
| `Fitten generate_unit_test` | Generate unit test |
| `Fitten implement_features` | Implement features |
| `Fitten improve_code` | Improve code |
| `Fitten refactor_code` | Refactor code |
| `Fitten guess_programming_language` | Guess programming language |
| `Fitten analyze_data` | Analyze data |
| `Fitten start_chat` | Start chat |

### Default Mappings

Expand Down Expand Up @@ -243,6 +246,8 @@ vim.log = {
| `implement_features(ImplementFeaturesOptions)` | Implement features |
| `improve_code(ActionOptions)` | Improve code |
| `refactor_code(ActionOptions)` | Refactor code |
| `guess_programming_language(ActionOptions)` | Guess programming language |
| `analyze_data(ActionOptions)` | Analyze data |
| `start_chat(ActionOptions)` | Start chat |
| `stop_eval()` | Stop the evaluation |

Expand Down
8 changes: 8 additions & 0 deletions lua/fittencode/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ M.api = {
return ActionsEngine.refactor_code(opts)
end,
---@param opts? ActionOptions
guess_programming_language = function(opts)
return ActionsEngine.guess_programming_language(opts)
end,
---@param opts? ActionOptions
analyze_data = function(opts)
return ActionsEngine.analyze_data(opts)
end,
---@param opts? ActionOptions
start_chat = function(opts)
return ActionsEngine.start_chat(opts)
end,
Expand Down
21 changes: 21 additions & 0 deletions lua/fittencode/bindings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,23 @@ local function _start_chat(...)
return _action_apis_wrap(API.start_chat, ...)
end

local function _action_apis_wrap_content(fx, ...)
local args = { ... }
---@type ActionOptions
local opts = {
content = args[1],
}
return fx(opts)
end

local function _guess_programming_language(...)
return _action_apis_wrap_content(API.guess_programming_language, ...)
end

local function _analyze_data(...)
return _action_apis_wrap_content(API.analyze_data, ...)
end

function M.setup_commands()
---@type FittenCommands
local commands = {
Expand Down Expand Up @@ -171,6 +188,10 @@ function M.setup_commands()
improve_code = _improve_code,
-- Arguments: language
refactor_code = _refactor_code,
-- Arguments: code
guess_programming_language = _guess_programming_language,
-- Arguments: data
analyze_data = _analyze_data,
-- Arguments: language
start_chat = _start_chat,
-- Arguments: Nop
Expand Down
22 changes: 20 additions & 2 deletions lua/fittencode/engines/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ local Actions = {
ImplementFeatures = 6,
ImproveCode = 7,
RefactorCode = 8,
GuessProgrammingLanguage = 9,
AnalyzeData = 10,
}

local current_eval = 1
Expand All @@ -60,7 +62,7 @@ local lock = false

local elapsed_time = 0
local depth = 0
local MAX_DEPTH = 10
local MAX_DEPTH = 20

local stop_eval = false

Expand Down Expand Up @@ -162,7 +164,7 @@ local function on_error(err)
end
Log.debug('Action elapsed time: {}', elapsed_time)
Log.debug('Action depth: {}', depth)
chat:commit('> Q.E.D.' .. '(' .. elapsed_time .. ' ms)' .. '\n', true)
chat:commit('> Q.E.D.' .. '(' .. elapsed_time .. ' ms)' .. '\n', true, true)
current_eval = current_eval + 1
end

Expand Down Expand Up @@ -442,6 +444,22 @@ function ActionsEngine.refactor_code(opts)
ActionsEngine.start_action(Actions.RefactorCode, merged)
end

---@param opts? ActionOptions
function ActionsEngine.guess_programming_language(opts)
local defaults = {
}
local merged = vim.tbl_deep_extend('force', defaults, opts or {})
ActionsEngine.start_action(Actions.GuessProgrammingLanguage, merged)
end

---@param opts? ActionOptions
function ActionsEngine.analyze_data(opts)
local defaults = {
}
local merged = vim.tbl_deep_extend('force', defaults, opts or {})
ActionsEngine.start_action(Actions.AnalyzeData, merged)
end

-- API: ActionOptions.content
---@param opts? ActionOptions
function ActionsEngine.start_chat(opts)
Expand Down
5 changes: 4 additions & 1 deletion lua/fittencode/fs/path.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ function M.nvim_sep()
return M.nt_sep()
end

function M.name(buffer)
function M.name(buffer, without_ext)
local path = api.nvim_buf_get_name(buffer)
if without_ext then
return fn.fnamemodify(path, ':t:r')
end
return fn.fnamemodify(path, ':t')
end

Expand Down
145 changes: 91 additions & 54 deletions lua/fittencode/prompt_providers/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ end
---@param buffer integer
---@param range ActionRange
---@return string
local function get_range_content(buffer, range)
local function make_range_content(buffer, range)
local lines = {}
if range.vmode then
lines = range.region or {}
Expand All @@ -56,79 +56,116 @@ local function get_range_content(buffer, range)
return table.concat(lines, '\n')
end

local NO_LANG_ACTIONS = { 'StartChat', 'GuessProgrammingLanguage', 'AnalyzeData' }

local MAP_ACTION_PROMPTS = {
StartChat = 'Answers the question above',
DocumentCode = 'Document the code above, commenting line by line',
EditCode = function(ctx)
return ctx.prompt
end,
ExplainCode = 'Explain the code above',
FindBugs = 'Find bugs in the code above',
GenerateUnitTest = function(ctx)
local opts = ctx.action_opts or {}
if opts.test_framework then
return 'Generate a unit test for the code above with ' .. opts.test_framework
end
return 'Generate a unit test for the code above'
end,
ImplementFeatures = function(ctx)
local opts = ctx.action_opts or {}
local feature_type = opts.feature_type or 'code'
return 'Implement the ' .. feature_type .. ' mentioned in the code above'
end,
ImproveCode = 'Improve the code above',
RefactorCode = 'Refactor the code above',
GuessProgrammingLanguage = 'Guess the programming language of the code above',
AnalyzeData = 'Analyze the data above',
}

local function make_language(ctx)
local filetype = ctx.filetype or ''
-- Log.debug('Action Filetype: {}', filetype)
local language = ctx.action_opts.language or filetype
-- Log.debug('Action Language: {}', language)
return language
end

local function make_content_with_prefix_suffix(ctx, language, no_lang)
local content = ''

if ctx.solved_content then
content = ctx.solved_content
else
content = make_range_content(ctx.buffer, ctx.range)
end

local content_prefix = '```'
local content_suffix = '```'
if not no_lang then
content_prefix = '```' .. language
end
content = content_prefix .. '\n' .. content .. '\n' .. content_suffix

return content
end

local function make_prompt(ctx, name, language, no_lang)
local key = MAP_ACTION_PROMPTS[name]
local lang_suffix = ''
if not no_lang then
lang_suffix = #language > 0 and ' in ' .. language or ''
end
local prompt = ctx.prompt or ((type(key) == 'function' and key(ctx) or key) .. lang_suffix)
return prompt
end

local function make_prefix(content, prompt)
local start_question = '# Question:\n'
local start_answer = '# Answer:\n'

local prefix = table.concat({
start_question,
content,
'\n',
start_answer,
'Dear FittenCode, Please ',
prompt,
-- ' and provide your feedback',
':',
}, '')
return prefix
end

---@param ctx PromptContext
---@return Prompt?
function M:execute(ctx)
if (not ctx.solved_prefix and not ctx.solved_content) and (not api.nvim_buf_is_valid(ctx.buffer) or ctx.range == nil) then
return
end

local name = ctx.prompt_ty:sub(#NAME + 2)
local no_lang = vim.tbl_contains(NO_LANG_ACTIONS, name)

local filename = ''
if ctx.buffer then
filename = Path.name(ctx.buffer)
filename = Path.name(ctx.buffer, no_lang)
end

local within_the_line = false
local content = ''

local prefix = ''
if ctx.solved_prefix then
prefix = ctx.solved_prefix
else
if ctx.solved_content then
content = ctx.solved_content
else
content = get_range_content(ctx.buffer, ctx.range)
end
local name = ctx.prompt_ty:sub(#NAME + 2)
Log.debug('Action Name: {}', name)
local filetype = ctx.filetype or ''
Log.debug('Action Filetype: {}', filetype)
local language = ctx.action_opts.language or filetype
Log.debug('Action Language: {}', language)
local content_prefix = '```'
local content_suffix = '```'
if name ~= 'StartChat' then
content_prefix = '```' .. language
end
content = content_prefix .. '\n' .. content .. '\n' .. content_suffix
local map_action_prompt = {
StartChat = 'Answers the question above',
DocumentCode = 'Document the code above',
EditCode = ctx.prompt,
ExplainCode = 'Explain the code above',
FindBugs = 'Find bugs in the code above',
GenerateUnitTest = function(opts)
opts = opts or {}
if opts.test_framework then
return 'Generate a unit test for the code above with ' .. opts.test_framework
end
return 'Generate a unit test for the code above'
end,
ImplementFeatures = function(opts)
opts = opts or {}
local feature_type = opts.feature_type or 'code'
return 'Implement the ' .. feature_type .. ' mentioned in the code above'
end,
ImproveCode = 'Improve the code above',
RefactorCode = 'Refactor the code above',
}
local key = map_action_prompt[name]
local lang_suffix = ''
if name ~= 'StartChat' then
lang_suffix = #language > 0 and ' in ' .. language or ''
end
local prompt = ctx.prompt or ((type(key) == 'function' and key(ctx.action_opts) or key) .. lang_suffix)
-- Log.debug('Action Prompt: {}', prompt)
local start_question = '# Question:\n'
local start_answer = '# Answer:\n'
prefix = start_question .. content .. '\n' .. start_answer .. 'Dear FittenCode, Please ' .. prompt .. ':\n'
local language = make_language(ctx)
content = make_content_with_prefix_suffix(ctx, language, no_lang)
local prompt = make_prompt(ctx, name, language, no_lang)
prefix = make_prefix(content, prompt)
end
local suffix = ''

-- Log.debug('Action Prefix: {}', prefix)
-- Log.debug('Action Suffix: {}', suffix)

return {
name = self.name,
priority = self.priority,
Expand Down
23 changes: 21 additions & 2 deletions lua/fittencode/views/chat.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,20 @@ function M:close()
-- self.buffer = nil
end

local stack = {}

local function push_stack(x)
if #stack == 0 then
table.insert(stack, x)
else
table.remove(stack)
end
end

---@param text? string|string[]
---@param linebreak? boolean
function M:commit(text, linebreak)
---@param force? boolean
function M:commit(text, linebreak, force)
local lines = nil
if type(text) == 'string' then
lines = vim.split(text, '\n')
Expand All @@ -79,8 +90,16 @@ function M:commit(text, linebreak)
else
return
end
vim.tbl_map(function(x)
if x:match('^```') then
push_stack(x)
end
end, lines)
if #stack > 0 and not force then
linebreak = false
end
if linebreak and #self.text > 0 and #lines > 0 then
if lines[1] ~= '' and self.text[#self.text] ~= '' then
if lines[1] ~= '' and not string.match(lines[1], '^```') and self.text[#self.text] ~= '' and not string.match(self.text[#self.text], '^```') then
table.insert(lines, 1, '')
end
end
Expand Down