From 197866529aff739429c96cb6440f50ba96cff14f Mon Sep 17 00:00:00 2001 From: luozhiya Date: Tue, 14 May 2024 18:07:57 +0800 Subject: [PATCH] Refactor accept 2 --- Makefile | 5 + lua/fittencode/bindings.lua | 7 + lua/fittencode/suggestions_cache.lua | 233 +++++++++++++++++---- lua/fittencode/suggestions_cache_tests.lua | 70 ------- tests/init.lua | 11 + tests/lua/CATCHME/suggestions_cache.lua | 99 +++++++++ 6 files changed, 316 insertions(+), 109 deletions(-) delete mode 100644 lua/fittencode/suggestions_cache_tests.lua create mode 100644 tests/init.lua create mode 100644 tests/lua/CATCHME/suggestions_cache.lua diff --git a/Makefile b/Makefile index f737b3e6..6113fabc 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: cf cf: CodeFormat format -c .editorconfig -w lua/ + CodeFormat format -c .editorconfig -w tests/ .PHONY: sl sl: @@ -9,3 +10,7 @@ sl: .PHONY: lint lint: luacheck . + +.PHONY: test +test: + nvim --headless -u tests/init.lua -c 'qa' diff --git a/lua/fittencode/bindings.lua b/lua/fittencode/bindings.lua index 32ec27f2..bebbc6c8 100644 --- a/lua/fittencode/bindings.lua +++ b/lua/fittencode/bindings.lua @@ -219,6 +219,13 @@ function M.setup_keymaps() end) Base.map('i', '', API.accept_line) Base.map('i', '', API.accept_word) + -- Base.map('i', '', function () + -- if API.has_suggestions() then + -- API.accept_suggestion() + -- else + -- Lines.enter() + -- end + -- end) end function M.setup_keyfilters() diff --git a/lua/fittencode/suggestions_cache.lua b/lua/fittencode/suggestions_cache.lua index f37a0748..fa5f5a5a 100644 --- a/lua/fittencode/suggestions_cache.lua +++ b/lua/fittencode/suggestions_cache.lua @@ -1,24 +1,19 @@ --- local Unicode = require('fittencode.unicode') -local Unicode = require('unicode') +local Unicode = require('fittencode.unicode') ---@class SuggestionsCache ---@field task_id? integer ---@field lines? string[] ----@field doc_cursor Cursor ----@field commit_cursor Cursor +---@field doc_cursor table +---@field commit_cursor table local SuggestionsCache = {} ----@class Cursor ----@field row? integer ----@field col? integer - function SuggestionsCache.new() local self = setmetatable({}, { __index = SuggestionsCache }) self.task_id = nil self.lines = {} self.utf_startpoints = {} self.doc_cursor = {} - self.commit_cursor = { row = 1, col = 1 } + self.commit_cursor = { 0, 0 } return self end @@ -34,8 +29,8 @@ function SuggestionsCache:update(task_id, row, col, lines) self.task_id = task_id self.lines = lines or {} self.utf_startpoints = Unicode.calculate_utf_startpoints_tbl(self.lines) - self.doc_cursor = { row = row, col = col } - self.commit_cursor = { row = 1, col = 1 } + self.doc_cursor = { row, col } + self.commit_cursor = { 0, 0 } end ---@param char string @@ -53,17 +48,51 @@ local function is_spacex(char) return byte == 32 or byte == 9 end -local function commit_post(lines, next_row, next_col) - if next_row < 1 then - next_row = 1 - next_col = 1 - end - if next_row > #lines then - next_row = #lines - next_col = lines[next_row]:len() - end - if next_col < 1 then - next_col = lines[next_row]:len() +-- row 取值范围 +-- 0 no start 没有上一行了,则设置col为0 +-- 1 lines[1] +-- n lines[n] +-- n+1 没有下一行了,则设置col为改行末尾 + +-- col 取值范围 +-- -1 需要换到上一列的末尾,如果没有上一列了,则设置col为0 +-- 0 行还没开始 +-- 1 第一个字符 +-- n 第n个字符 +-- n+1 超出了当前行,需要跳到下一行的开始,如果没有下一行了,则设置col为末尾 +local function postcommit(lines, next_row, next_col) + if next_col == -1 then + next_row = next_row - 1 + if next_row <= 0 then + next_row = 0 + next_col = 0 + else + next_col = string.len(lines[next_row]) + end + elseif next_col > 0 then + if next_row == 0 then + next_row = 1 + end + if next_row > #lines then + next_row = #lines + next_col = string.len(lines[next_row]) + else + if next_col == string.len(lines[next_row]) + 1 then + if next_row == #lines then + next_col = string.len(lines[next_row]) + else + next_row = next_row + 1 + next_col = 0 + end + end + end + elseif next_col == 0 then + -- if next_row == #lines + 1 then + -- next_row = #lines + -- next_col = string.len(lines[next_row]) + -- elseif next_row == 1 then + -- next_row = 0 + -- end end return next_row, next_col end @@ -106,7 +135,7 @@ end -- Calculate the next word index, split by word boundary ---@param line string -local function calculate_next_col_by_char(line, utf_sp, next_col, forward) +local function calculate_next_col_by_word(line, utf_sp, next_col, forward) -- next_col = next_col + (forward and 1 or -1) if forward and next_col == string.len(line) then return next_col + 1 @@ -125,37 +154,163 @@ local function calculate_next_col_by_char(line, utf_sp, next_col, forward) return col end -function SuggestionsCache:commit_word(forward) - local next_row = self.commit_cursor.row - local next_col = self.commit_cursor.col +local function find_zero_reverse(tbl, start) + if not start then + return + end + for i = start, 1, -1 do + if tbl[i] == 0 then + return i + end + end +end + +local function find_zero(tbl, start) + for i = start, #tbl do + if tbl[i] == 0 then + return i + end + end +end + +local function find_diff(line, utf_sp, start, current) + local x = find_zero(utf_sp, start) + if x ~= start then + return x - start + 1 + end + local is_space = is_spacex(current) + for i = start, #line do + if line[i] ~= current then + return i + end + end +end + +local function find_diff_reverse(line, utf_sp, start, current) + for i = start, 1, -1 do + if line[i] ~= current then + return i + end + end +end + +local function calculate_offset(unit, line, utf_sp, col, forward) + local offset = (forward and 1 or -1) + + -- 1 2 3 4 5 6 7 8 + -- { 0, 0, 0, 0, 0, 0, -1, -2 } + if unit == 'char' then + if forward then + local x = find_zero(utf_sp, col + 1) + if x then + offset = x - col + else + offset = #line - col + 1 + end + else + local x = find_zero_reverse(utf_sp, col - 1) + if x then + offset = x - col + end + end + end + + if unit == 'word' then + return calculate_next_col_by_word(line, utf_sp, col, forward) - col - next_col = calculate_next_col_by_char(self.lines[next_row], self.utf_startpoints[next_row], next_col, forward) - print("commit_word", next_row, next_col) - if next_col > string.len(self.lines[next_row]) then - next_row = next_row + 1 - next_col = 1 + -- local current = line[col] + -- if forward then + -- local x = find_diff(line, col + 1, current) + -- if x then + -- offset = x - col + -- else + -- offset = #line - col + 1 + -- end + -- else + -- local x = find_diff_reverse(line, col - 1, current) + -- if x then + -- offset = x - col + -- end + -- end end + + return offset +end + +local function precommit(lines, next_row, next_col, forward) if next_col == 0 then - next_row = next_row - 1 + if not forward then + next_row = next_row - 1 + if next_row > 0 then + next_col = string.len(lines[next_row]) + 1 + else + next_row = 0 + end + else + if next_row == 0 then + next_row = 1 + end + end + elseif next_col == string.len(lines[next_row]) then + if forward then + next_row = next_row + 1 + next_col = 0 + if next_row > #lines then + next_row = #lines + next_col = string.len(lines[next_row]) + end + end + end + return next_row, next_col +end + +function SuggestionsCache:commit_char(forward) + local next_row = self.commit_cursor[1] + local next_col = self.commit_cursor[2] + + -- 1. Compute next_row, make next_col on next_row + next_row, next_col = precommit(self.lines, next_row, next_col, forward) + + -- 2. Compute next_col + local offset = (forward and 1 or -1) + if next_row >= 1 and next_row <= #self.lines then + offset = calculate_offset('char', self.lines[next_row], self.utf_startpoints[next_row], next_col, forward) end - next_row, next_col = commit_post(self.lines, next_row, next_col) - self.commit_cursor = { row = next_row, col = next_col } + next_col = next_col + offset + + -- 3. Fixup next_row and next_col + next_row, next_col = postcommit(self.lines, next_row, next_col) + self.commit_cursor = { next_row, next_col } +end + +function SuggestionsCache:commit_word(forward) + local next_row = self.commit_cursor[1] + local next_col = self.commit_cursor[2] + + next_row, next_col = precommit(self.lines, next_row, next_col, forward) + local offset = calculate_offset('word', self.lines[next_row], self.utf_startpoints[next_row], next_col, forward) + next_col = next_col + offset + + next_row, next_col = postcommit(self.lines, next_row, next_col) + self.commit_cursor = { next_row, next_col } end ---@param forward boolean function SuggestionsCache:commit_line(forward) - local next_row = self.commit_cursor.row - local next_col = self.commit_cursor.col + local next_row = self.commit_cursor[1] + local next_col = self.commit_cursor[2] + + next_row, next_col = precommit(self.lines, next_row, next_col, forward) next_row = next_row + (forward and 1 or -1) next_col = 1 - next_row, next_col = commit_post(self.lines, next_row, next_col) - self.commit_cursor = { row = next_row, col = next_col } + next_row, next_col = postcommit(self.lines, next_row, next_col) + self.commit_cursor = { next_row, next_col } end function SuggestionsCache:commit_all() - self.commit_cursor = { row = #self.lines, col = self.lines[#self.lines]:len() } + self.commit_cursor = { #self.lines, self.lines[#self.lines]:len() } end function SuggestionsCache:is_commit_reach_end() diff --git a/lua/fittencode/suggestions_cache_tests.lua b/lua/fittencode/suggestions_cache_tests.lua deleted file mode 100644 index 084428e5..00000000 --- a/lua/fittencode/suggestions_cache_tests.lua +++ /dev/null @@ -1,70 +0,0 @@ -local SuggestionsCache = require('suggestions_cache') - -local count = 1 - -local function run_case(func) - print('Test case', count) - func() - print('Test case', count, 'done') - count = count + 1 -end - -run_case(function() - local cache = SuggestionsCache:new() - - cache:update(1, 0, 0, { - 'hello', - 'world' - }) - cache:commit_word(true) - assert(cache.commit_cursor.row == 1) - assert(cache.commit_cursor.col == 5) - - cache:commit_word(true) - assert(cache.commit_cursor.row == 2) - assert(cache.commit_cursor.col == 1) - - cache:commit_word(true) - assert(cache.commit_cursor.row == 2) - assert(cache.commit_cursor.col == 5) - - cache:commit_word(true) - assert(cache.commit_cursor.row == 2) - assert(cache.commit_cursor.col == 5) - - cache:commit_word(false) - assert(cache.commit_cursor.row == 2) - assert(cache.commit_cursor.col == 1) - - -- cache.commit_cursor.row = 2 - -- cache.commit_cursor.col = 1 - cache:commit_word(false) - assert(cache.commit_cursor.row == 1) - assert(cache.commit_cursor.col == 5) - - cache:commit_word(false) - assert(cache.commit_cursor.row == 1) - assert(cache.commit_cursor.col == 1) - - cache:commit_word(false) - assert(cache.commit_cursor.row == 1) - assert(cache.commit_cursor.col == 1) -end) - -run_case(function() - local cache = SuggestionsCache:new() - cache:update(1, 0, 0, { - 'hello中', - 'world' - }) - print(vim.inspect(cache.utf_startpoints)) - - cache:commit_word(true) - assert(cache.commit_cursor.row == 1) - assert(cache.commit_cursor.col == 5) - - cache:commit_word(true) - print(vim.inspect(cache.commit_cursor)) - assert(cache.commit_cursor.row == 1) - assert(cache.commit_cursor.col == 8) -end) diff --git a/tests/init.lua b/tests/init.lua new file mode 100644 index 00000000..633f640b --- /dev/null +++ b/tests/init.lua @@ -0,0 +1,11 @@ +local function root(root) + -- "S" -- `source`, `short_src`, `linedefined`, `lastlinedefined`, and `what` + local f = debug.getinfo(1, 'S').source:sub(2) + return vim.fn.fnamemodify(f, ':p:h:h') .. '/' .. (root or '') +end + +vim.opt.runtimepath:append(root('')) +vim.opt.runtimepath:append(root('tests')) + +-- local SuggestionsCacheTest = require('CATCHME.suggestions_cache') +local UVTest = require('CATCHME.uv') diff --git a/tests/lua/CATCHME/suggestions_cache.lua b/tests/lua/CATCHME/suggestions_cache.lua new file mode 100644 index 00000000..5639852b --- /dev/null +++ b/tests/lua/CATCHME/suggestions_cache.lua @@ -0,0 +1,99 @@ +local SuggestionsCache = require('fittencode.suggestions_cache') + +local count = 1 + +local function run_case(run, func) + if not run then + return + end + print('>', count) + func() + print('<', count, 'DONE') + count = count + 1 +end + +run_case(false, function() + local cache = SuggestionsCache:new() + + cache:update(1, 0, 0, { + 'hello', + 'world!' + }) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(true) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(true) + cache:commit_char(true) + cache:commit_char(true) + cache:commit_char(true) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(true) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(true) + cache:commit_char(true) + cache:commit_char(true) + cache:commit_char(true) + cache:commit_char(true) + cache:commit_char(true) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(true) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(false) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(false) + cache:commit_char(false) + cache:commit_char(false) + cache:commit_char(false) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(false) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(false) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(false) + cache:commit_char(false) + cache:commit_char(false) + cache:commit_char(false) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(false) + print(vim.inspect(cache.commit_cursor)) + cache:commit_char(false) + print(vim.inspect(cache.commit_cursor)) +end) + +run_case(false, function() + local cache = SuggestionsCache:new() + cache:update(1, 0, 0, { + 'hello中', + 'world' + }) + print(vim.inspect(cache.utf_startpoints)) + print(vim.inspect(cache.commit_cursor)) + print('forward') + for i = 1, 15 do + cache:commit_char(true) + print(i, vim.inspect(cache.commit_cursor)) + end + print('backward') + for i = 1, 15 do + cache:commit_char(false) + print(i, vim.inspect(cache.commit_cursor)) + end +end) + +run_case(true, function() + local cache = SuggestionsCache:new() + cache:update(1, 0, 0, { + 'hello world', + 'hello lua' + }) + print('forward') + for i = 1, 15 do + cache:commit_word(true) + print(i, vim.inspect(cache.commit_cursor)) + end + print('backward') + for i = 1, 15 do + cache:commit_word(false) + print(i, vim.inspect(cache.commit_cursor)) + end +end)