| | if !exists("g:whisper_dir") |
| | let g:whisper_dir = expand($WHISPER_CPP_HOME) |
| | if g:whisper_dir == "" |
| | echoerr "Please provide a path to the whisper.cpp repo in either the $WHISPER_CPP_HOME environment variable, or g:whisper_dir" |
| | endif |
| | endif |
| | if !exists("g:whisper_lsp_path") |
| | let g:whisper_lsp_path = g:whisper_dir .. "lsp" |
| | if !filereadable(g:whisper_lsp_path) |
| | echoerr "Was not able to locate a lsp executable at: " .. g:whisper_lsp_path |
| | throw "Executable not found" |
| | endif |
| | endif |
| | if !exists("g:whisper_model_path") |
| | |
| | let g:whisper_model_path = g:whisper_dir .. "models/ggml-base.en.bin" |
| | if !filereadable(g:whisper_model_path) |
| | echoerr "Could not find model at: " .. g:whisper_model_path |
| | throw "Model not found" |
| | endif |
| | endif |
| | let s:output_buffer = bufnr("whisper_log", v:true) |
| | call setbufvar(s:output_buffer,"&buftype","nofile") |
| | let s:lsp_command = [g:whisper_lsp_path,"-m",g:whisper_model_path] |
| | |
| | " let s:lsp_command = [g:whisper_lsp_path, "-m", g:whisper_dir .. "models/ggml-tiny.en.bin", "-ac", "128 |
| |
|
| | |
| | func whisper#requestCommands(...) |
| | let l:req = {"method": "guided", "params": {"commandset_index": 0}} |
| | if a:0 > 0 |
| | call extend(l:req.params, a:1) |
| | endif |
| | let resp = ch_sendexpr(g:lsp_job, l:req, {"callback": function("s:commandCallback", [l:req.params, 0])}) |
| | endfunction |
| |
|
| | |
| | func whisper#doTranscription(...) |
| | let l:req = {"method": "unguided", "params": {}} |
| | if a:0 > 0 |
| | call extend(l:req.params, a:1) |
| | endif |
| | let resp = ch_sendexpr(g:lsp_job, l:req, {"callback": function("s:transcriptionCallback", [function("s:insertText"),function("s:endTranscription")])}) |
| | endfunction |
| |
|
| | |
| | func whisper#uppertest(cha) |
| | echo tr(a:cha, s:c_lowerkeys, s:c_upperkeys) |
| | endfunction |
| |
|
| |
|
| | " (upper, exit, count, motion, command, insert/append, save run) "base |
| | " (upper, exit, count, motion, command, inside/around) "motion/visual |
| | " (upper, exit, count, motion, line, inside/around) "command already entered |
| | " (upper, exit, key, ) "from/till |
| |
|
| | |
| | |
| | let s:c_lowerkeys = "1234567890-=qwertyuiop[]\\asdfghjkl;'zxcvbnm,./\"" |
| | let s:c_upperkeys = "!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:\"ZXCVBNM<>?'" |
| | let s:c_count = split("1234567890\"",'\zs') |
| | let s:c_command = split("ryuogpdxcv.iam", '\zs') |
| | let s:c_motion = split("wetf'hjklnb$^)",'\zs') |
| | " object words: Word, Sentence, Paragraph, [, (, <, Tag, {. ", ' |
| | let s:c_area = split("wsp])>t}\"' |
| | |
| | let s:c_special_always = ["exit", "upper"] |
| | let s:c_special_normal = ["save", "run", "space"] |
| |
|
| | |
| | |
| | " If key resolves to dict, {0: "normal",1: "motion",2:"single char",3: "area |
| | " Missing entries fall back as follows {0: "required", 1: 0, 2: "key |
| | let s:spoken_dict = {"w": "word", "e": "end", "r": "replace", "t": {0: "till", 3: "tag"}, "y": "yank", "u": "undo", "i": {0: "insert", 1: "inside"}, "o": "open", "p": {0: "paste", 3: "paragraph"}, "a": {0: "append", 1: "around"}, "s": {0: "substitute", 3: "sentence"}, "d": "delete", "f": "from", "g": "go", "h": "left", "j": "down", "k": "up", "l": "right", "c": "change", "v": "visual", "b": "back", "n": "next", "m": "mark", ".": {0: "repeat", 2: "period"}, "]": {0: "bracket", 2: "bracket"}, "'": {0: "jump", 2: "apostrophe", 3: "apostrophe"}, '"': {0: 'register', 2: "quotation", 3: "quotation"}, "-": {0: "minus", 2: "minus"}, "$": {0: "dollar", 2: "dollar"}, "^": {0: "carrot", 2: "carrot"}, ")": {0: "sentence", 2: "parenthesis", 3: "parenthesis"}, "}": {0: "paragraph", 2: "brace", 3: "brace"}, ">": {0: "indent", 2: "angle", 3: "angle"}} |
| |
|
| | |
| | let s:sub_tran_msg = "" |
| | func s:subTranProg(msg) |
| | if s:sub_tran_msg != "" |
| | let s:sub_tran_msg = s:sub_tran_msg .. a:msg |
| | if mode() !=? 'v' |
| | exe "normal" "u" .. s:sub_tran_msg |
| | endif |
| | else |
| | if s:command_backlog == "" |
| | |
| | call s:logCallback(0, "Warning: Encountered sub transcription without prior command") |
| | let s:command_backlog = "a" |
| | endif |
| | if a:msg[0] == ' ' |
| | let s:sub_tran_msg = s:command_backlog .. a:msg[1:-1] |
| | else |
| | let s:sub_tran_msg = s:command_backlog .. a:msg |
| | endif |
| | if mode() !=? 'v' |
| | exe "normal" s:sub_tran_msg |
| | endif |
| | endif |
| | call appendbufline(s:output_buffer, "$", s:sub_tran_msg .. ":" .. string(a:msg )) |
| | endfunction |
| |
|
| | func s:subTranFinish(params, timestamp) |
| | let s:repeat_command = s:sub_tran_msg |
| | |
| | |
| | if mode() ==? 'v' |
| | exe "normal" s:sub_tran_msg |
| | endif |
| | let s:sub_tran_msg = "" |
| | let s:command_backlog = "" |
| | exe "normal a\<C-G>u" |
| | let l:params = a:params |
| | let l:params.timestamp = a:timestamp |
| | if exists("l:params.commandset_index") |
| | unlet l:params.commandset_index |
| | endif |
| | call whisper#requestCommands(a:params) |
| | endfunction |
| |
|
| | func s:logCallback(channel, msg) |
| | call appendbufline(s:output_buffer,"$",a:msg) |
| | endfunction |
| |
|
| |
|
| | func s:transcriptionCallback(progressCallback, finishedCallback, channel, msg) |
| | let l:tr = a:msg.result.transcription |
| |
|
| | let l:ex_ind = match(tolower(l:tr),"exit", len(l:tr)-6) |
| | " The worst case I've observed so far is " Exit. |
| | if l:ex_ind != -1 |
| | call a:progressCallback(strpart(l:tr,0,l:ex_ind-1)) |
| | call a:finishedCallback(a:msg.result.timestamp) |
| | else |
| | call a:progressCallback(l:tr) |
| | let req = {"method": "unguided", "params": {"timestamp": a:msg.result.timestamp, "no_context": v:true}} |
| | let resp = ch_sendexpr(g:lsp_job, req, {"callback": function("s:transcriptionCallback", [a:progressCallback, a:finishedCallback])}) |
| | endif |
| | endfunc |
| | func s:insertText(msg) |
| | exe "normal a" .. a:msg |
| | endfunction |
| | func s:endTranscription(timestamp) |
| | call appendbufline(s:output_buffer, "$", "Ending unguided transcription") |
| | endfunction |
| |
|
| |
|
| |
|
| | |
| | |
| | |
| | let s:command_backlog = "" |
| | let s:repeat_command = "" |
| | let s:preceeding_upper = v:false |
| | func s:commandCallback(params, commandset_index, channel, msg) |
| | let l:command_index = a:msg.result.command_index |
| | let l:do_execute = v:false |
| | let l:next_mode = a:commandset_index |
| | let l:command = s:commandset_list[a:commandset_index][l:command_index] |
| | call s:logCallback(0, string(a:msg) .. " " .. a:commandset_index .. " " .. l:command) |
| | if l:command_index == 0 |
| | |
| | "if s:command_backlog == " |
| | call s:logCallback(0,"Stopping command mode") |
| | echo "No longer listening" |
| | let s:command_backlog = "" |
| | return |
| | |
| | |
| | |
| | |
| | " call s:logCallback(0,"Clearing command_backlog |
| | " let s:command_backlog = " |
| | |
| | |
| | elseif l:command_index == 1 |
| | |
| | let s:preceeding_upper = !s:preceeding_upper |
| | elseif l:command == "save" |
| | |
| | exe "w" |
| | elseif l:command == "run" |
| | exe "make run" |
| | elseif l:command == "space" |
| | exe "normal i \<ESC>l" |
| | elseif has_key(s:c_user, l:command) |
| | let Userfunc = s:c_user[l:command] |
| | if type(Userfunc) == v:t_string |
| | let Userfunc = function(Userfunc) |
| | endif |
| | call Userfunc() |
| | else |
| | if s:preceeding_upper |
| | |
| | let s:preceeding_upper = v:false |
| | let l:visual_command = tr(l:command, s:c_lowerkeys, s:c_upperkeys) |
| | else |
| | let l:visual_command = l:command |
| | endif |
| | echo s:command_backlog .. " - " .. l:visual_command |
| | let s:command_backlog = s:command_backlog .. l:visual_command |
| | if a:commandset_index == 2 || a:commandset_index == 3 |
| | |
| | |
| | |
| | if s:command_backlog[-2:-2] !=# '"' |
| | call s:logCallback(0,"not register") |
| | let l:do_execute = v:true |
| | end |
| | let l:next_mode = 0 |
| | |
| | elseif (l:command == "a" || l:command == "i") && a:commandset_index == 1 |
| | |
| | let l:next_mode = 3 |
| | elseif l:command ==# '"' |
| | let l:next_mode = 2 |
| | elseif index(s:c_count, l:command) != -1 |
| | let l:next_mode = a:commandset_index |
| | elseif index(s:c_motion, l:command) != -1 |
| | if l:command == 't' || l:command == 'f' || l:command == "'" |
| | |
| | let l:next_mode = 2 |
| | else |
| | let l:do_execute = v:true |
| | let l:next_mode = 0 |
| | endif |
| | elseif index(s:c_command, l:command) != -1 |
| | if index(["y","g","d","c"], s:command_backlog[-1:-1]) != -1 && s:command_backlog[-1:-1] != s:command_backlog[-2:-2] && mode() !=? 'v' |
| | |
| | |
| | |
| | |
| | let l:next_mode = 1 |
| | elseif index(["i","a","c", "o", "s"], l:command) != -1 || s:command_backlog[-1:-1] ==# 'R' |
| | |
| | let l:req = {"method": "unguided", "params": a:params} |
| | let l:req.params.timestamp = a:msg.result.timestamp |
| | let l:req.params.no_context = v:true |
| | let resp = ch_sendexpr(g:lsp_job, req, {"callback": function("s:transcriptionCallback", [function("s:subTranProg"), function("s:subTranFinish", [a:params])])}) |
| | return |
| | elseif l:command == 'r' || l:command == 'm' |
| | let l:next_mode = 2 |
| | elseif l:command == '.' |
| | let l:next_mode = 0 |
| | let l:do_execute = v:true |
| | let s:command_backlog = s:command_backlog[0:-2] .. s:repeat_command |
| | else |
| | if l:command ==? 'v' |
| | let l:next_mode = 1 |
| | else |
| | let l:next_mode = 0 |
| | endif |
| | let l:do_execute = v:true |
| | endif |
| | else |
| | throw "Invalid command state: " .. l:command .. " " .. a:commandset_index .. " " .. s:command_backlog |
| | endif |
| | endif |
| | if l:do_execute |
| | if mode() ==?'v' && l:next_mode == 0 |
| | let l:next_mode = 1 |
| | elseif match(s:command_backlog, 'c') != -1 |
| | let l:req = {"method": "unguided", "params": a:params} |
| | let l:req.params.timestamp = a:msg.result.timestamp |
| | let l:req.params.no_context = v:true |
| | let resp = ch_sendexpr(g:lsp_job, req, {"callback": function("s:transcriptionCallback", [function("s:subTranProg"), function("s:subTranFinish", [a:params])])}) |
| | return |
| | endif |
| | exe "normal" s:command_backlog |
| | if index(s:c_motion + ["u"],l:command) == -1 |
| | exe "normal a\<C-G>u" |
| | let s:repeat_command = s:command_backlog |
| | call s:logCallback(0, s:command_backlog) |
| | endif |
| | let s:command_backlog = "" |
| | endif |
| | let l:req = {"method": "guided", "params": a:params} |
| | let l:req.params.timestamp = a:msg.result.timestamp |
| | let l:req.params.commandset_index = l:next_mode |
| | let resp = ch_sendexpr(g:lsp_job, l:req, {"callback": function("s:commandCallback",[a:params, l:next_mode])}) |
| | endfunction |
| |
|
| | func s:loadedCallback(channel, msg) |
| | echo "Loading complete" |
| | call s:logCallback(a:channel, a:msg) |
| | endfunction |
| |
|
| | func s:registerCommandset(commandlist, is_final) |
| | let req = {"method": "registerCommandset"} |
| | let req.params = a:commandlist |
| | call s:logCallback(0, join(a:commandlist)) |
| | call add(g:whisper_commandlist_spoken, a:commandlist) |
| | if a:is_final |
| | let resp = ch_sendexpr(g:lsp_job, req, {"callback": "s:loadedCallback"}) |
| | else |
| | let resp = ch_sendexpr(g:lsp_job, req, {"callback": "s:logCallback"}) |
| | endif |
| | endfunction |
| |
|
| | func s:registerAllCommands() |
| | let l:normal = s:c_special_always + s:c_special_normal + s:c_count + s:c_command + s:c_motion + keys(s:c_user) |
| | let l:visual = s:c_special_always + s:c_count + s:c_command + s:c_motion |
| | |
| | |
| | let l:single_key = s:c_special_always + split(s:c_lowerkeys, '\zs') |
| | let l:area = s:c_special_always + s:c_area |
| |
|
| | |
| | let g:whisper_commandlist_spoken = [] |
| |
|
| | let s:commandset_list = [l:normal, l:visual, l:single_key, l:area] |
| | call s:registerCommandset(s:commandsetToSpoken(l:normal, 0), v:false) |
| | call s:registerCommandset(s:commandsetToSpoken(l:visual, 1), v:false) |
| | call s:registerCommandset(s:commandsetToSpoken(l:single_key, 2), v:false) |
| | call s:registerCommandset(s:commandsetToSpoken(l:area, 3), v:true) |
| | endfunction |
| |
|
| | func s:commandsetToSpoken(commandset, spoken_index) |
| | let l:spoken_list = [] |
| | for l:command in a:commandset |
| | if has_key(s:spoken_dict, l:command) |
| | let l:spoken_value = s:spoken_dict[l:command] |
| | if type(l:spoken_value) == v:t_dict |
| | if has_key(l:spoken_value, a:spoken_index) |
| | let l:spoken_value = l:spoken_value[a:spoken_index] |
| | else |
| | if a:spoken_index == 2 |
| | let l:spoken_value = l:command |
| | else |
| | let l:spoken_value = l:spoken_value[0] |
| | endif |
| | endif |
| | else |
| | if a:spoken_index == 2 |
| | let l:spoken_value = l:command |
| | endif |
| | endif |
| | else |
| | let l:spoken_value = l:command |
| | endif |
| | call add(l:spoken_list, l:spoken_value) |
| | endfor |
| | return l:spoken_list |
| | endfunction |
| |
|
| | |
| | |
| | |
| | |
| | |
| | let s:lsp_opts = {"in_mode": "lsp", "out_mode": "lsp", "err_mode": "nl", "err_io": "buffer", "err_buf": s:output_buffer} |
| | if !exists("g:lsp_job") |
| | if exists("g:whisper_user_commands") |
| | let s:c_user = g:whisper_user_commands |
| | else |
| | let s:c_user = {} |
| | endif |
| | let g:lsp_job = job_start(s:lsp_command, s:lsp_opts) |
| | if job_status(g:lsp_job) == "fail" |
| | echoerr "Failed to start whisper job" |
| | endif |
| | call s:registerAllCommands() |
| | endif |
| |
|