2
2
"
3
3
" requires:
4
4
"
5
- " - neovim
5
+ " - neovim or vim
6
6
" - curl
7
7
" - llama.cpp server instance
8
8
" - FIM-compatible model
9
9
"
10
10
" sample config:
11
11
"
12
12
" - Tab - accept the current suggestion
13
- " - Shift+Tab - accept just the first line of the segguestion
13
+ " - Shift+Tab - accept just the first line of the suggestion
14
14
" - Ctrl+F - toggle FIM completion manually
15
15
"
16
16
" make symlink or copy this file to ~/.config/nvim/autoload/llama.vim
43
43
"
44
44
45
45
" colors (adjust to your liking)
46
- highlight llama_hl_hint guifg= #ff772f
47
- highlight llama_hl_info guifg= #77 ff2f
46
+ highlight llama_hl_hint guifg= #ff772f ctermfg = 202
47
+ highlight llama_hl_info guifg= #77 ff2f ctermfg = 119
48
48
49
49
" general parameters:
50
50
"
@@ -81,7 +81,7 @@ let s:default_config = {
81
81
\ ' n_suffix' : 64 ,
82
82
\ ' n_predict' : 128 ,
83
83
\ ' t_max_prompt_ms' : 500 ,
84
- \ ' t_max_predict_ms' : 1000 ,
84
+ \ ' t_max_predict_ms' : 3000 ,
85
85
\ ' show_info' : 2 ,
86
86
\ ' auto_fim' : v: true ,
87
87
\ ' max_line_suffix' : 8 ,
@@ -93,6 +93,18 @@ let s:default_config = {
93
93
94
94
let g: llama_config = get (g: , ' llama_config' , s: default_config )
95
95
96
+ function ! s: get_indent (str)
97
+ let l: count = 0
98
+ for i in range (len (a: str ))
99
+ if a: str [i ] == " \t "
100
+ let l: count += &tabstop - 1
101
+ else
102
+ break
103
+ endif
104
+ endfor
105
+ return l: count
106
+ endfunction
107
+
96
108
function ! s: rand (i0, i1) abort
97
109
return a: i0 + rand () % (a: i1 - a: i0 + 1 )
98
110
endfunction
@@ -129,6 +141,21 @@ function! llama#init()
129
141
130
142
let s: current_job = v: null
131
143
144
+ let s: ghost_text_nvim = exists (' *nvim_buf_get_mark' )
145
+ let s: ghost_text_vim = has (' textprop' )
146
+
147
+ if s: ghost_text_vim
148
+ let s: hlgroup_hint = ' llama_hl_hint'
149
+ let s: hlgroup_info = ' llama_hl_info'
150
+
151
+ if empty (prop_type_get (s: hlgroup_hint ))
152
+ call prop_type_add (s: hlgroup_hint , {' highlight' : s: hlgroup_hint })
153
+ endif
154
+ if empty (prop_type_get (s: hlgroup_info ))
155
+ call prop_type_add (s: hlgroup_info , {' highlight' : s: hlgroup_info })
156
+ endif
157
+ endif
158
+
132
159
augroup llama
133
160
autocmd !
134
161
autocmd InsertEnter * inoremap <expr> <silent> <C-F> llama#fim_inline(v:false)
@@ -317,13 +344,22 @@ function! s:ring_update()
317
344
\ ' t_max_predict_ms' : 1
318
345
\ })
319
346
320
- let l: curl_command = printf (
321
- \ " curl --silent --no-buffer --request POST --url %s --header \" Content-Type: application/json\" --data %s" ,
322
- \ g: llama_config .endpoint, shellescape (l: request )
323
- \ )
347
+ let l: curl_command = [
348
+ \ " curl" ,
349
+ \ " --silent" ,
350
+ \ " --no-buffer" ,
351
+ \ " --request" , " POST" ,
352
+ \ " --url" , g: llama_config .endpoint,
353
+ \ " --header" , " Content-Type: application/json" ,
354
+ \ " --data" , l: request
355
+ \ ]
324
356
325
357
" no callbacks because we don't need to process the response
326
- call jobstart (l: curl_command , {})
358
+ if s: ghost_text_nvim
359
+ call jobstart (l: curl_command , {})
360
+ elseif s: ghost_text_vim
361
+ call job_start (l: curl_command , {})
362
+ endif
327
363
endfunction
328
364
329
365
" necessary for 'inoremap <expr>'
@@ -418,24 +454,37 @@ function! llama#fim(is_auto) abort
418
454
\ ' t_max_predict_ms' : g: llama_config .t_max_predict_ms
419
455
\ })
420
456
421
- let l: curl_command = printf (
422
- \ " curl --silent --no-buffer --request POST --url %s --header \" Content-Type: application/json\" --data %s" ,
423
- \ g: llama_config .endpoint, shellescape (l: request )
424
- \ )
457
+ let l: curl_command = [
458
+ \ " curl" ,
459
+ \ " --silent" ,
460
+ \ " --no-buffer" ,
461
+ \ " --request" , " POST" ,
462
+ \ " --url" , g: llama_config .endpoint,
463
+ \ " --header" , " Content-Type: application/json" ,
464
+ \ " --data" , l: request
465
+ \ ]
425
466
426
467
if s: current_job != v: null
427
- call jobstop (s: current_job )
468
+ if s: ghost_text_nvim
469
+ call jobstop (s: current_job )
470
+ elseif s: ghost_text_vim
471
+ call job_stop (s: current_job )
472
+ endif
428
473
endif
429
474
430
475
" send the request asynchronously
431
- let s: current_job = jobstart (l: curl_command , {
432
- \ ' on_stdout' : function (' s:fim_on_stdout' ),
433
- \ ' on_exit' : function (' s:fim_on_exit' ),
434
- \ ' stdout_buffered' : v: true ,
435
- \ ' pos_x' : s: pos_x ,
436
- \ ' pos_y' : s: pos_y ,
437
- \ ' is_auto' : a: is_auto
438
- \ })
476
+ if s: ghost_text_nvim
477
+ let s: current_job = jobstart (l: curl_command , {
478
+ \ ' on_stdout' : function (' s:fim_on_stdout' , [s: pos_x , s: pos_y , a: is_auto ]),
479
+ \ ' on_exit' : function (' s:fim_on_exit' ),
480
+ \ ' stdout_buffered' : v: true
481
+ \ })
482
+ elseif s: ghost_text_vim
483
+ let s: current_job = job_start (l: curl_command , {
484
+ \ ' out_cb' : function (' s:fim_on_stdout' , [s: pos_x , s: pos_y , a: is_auto ]),
485
+ \ ' exit_cb' : function (' s:fim_on_exit' )
486
+ \ })
487
+ endif
439
488
440
489
" TODO: per-file location
441
490
let l: delta_y = abs (s: pos_y - s: pos_y_pick )
@@ -482,9 +531,13 @@ function! llama#fim_cancel()
482
531
" clear the virtual text
483
532
let l: bufnr = bufnr (' %' )
484
533
485
- let l: id_vt_fim = nvim_create_namespace (' vt_fim' )
486
-
487
- call nvim_buf_clear_namespace (l: bufnr , l: id_vt_fim , 0 , -1 )
534
+ if s: ghost_text_nvim
535
+ let l: id_vt_fim = nvim_create_namespace (' vt_fim' )
536
+ call nvim_buf_clear_namespace (l: bufnr , l: id_vt_fim , 0 , -1 )
537
+ elseif s: ghost_text_vim
538
+ call prop_remove ({' type' : s: hlgroup_hint , ' all' : v: true })
539
+ call prop_remove ({' type' : s: hlgroup_info , ' all' : v: true })
540
+ endif
488
541
489
542
" remove the mappings
490
543
silent ! iunmap <buffer> <Tab>
@@ -499,13 +552,18 @@ function! s:on_move()
499
552
endfunction
500
553
501
554
" callback that processes the FIM result from the server and displays the suggestion
502
- function ! s: fim_on_stdout (job_id, data, event ) dict
503
- let l: raw = join (a: data , " \n " )
555
+ function ! s: fim_on_stdout (pos_x, pos_y, is_auto, job_id, data, event = v: null )
556
+ if s: ghost_text_nvim
557
+ let l: raw = join (a: data , " \n " )
558
+ elseif s: ghost_text_vim
559
+ let l: raw = a: data
560
+ endif
561
+
504
562
if len (l: raw ) == 0
505
563
return
506
564
endif
507
565
508
- if self . pos_x != col (' .' ) - 1 || self . pos_y != line (' .' )
566
+ if a: pos_x != col (' .' ) - 1 || a: pos_y != line (' .' )
509
567
return
510
568
endif
511
569
@@ -514,14 +572,14 @@ function! s:fim_on_stdout(job_id, data, event) dict
514
572
return
515
573
endif
516
574
517
- let s: pos_x = self . pos_x
518
- let s: pos_y = self . pos_y
575
+ let s: pos_x = a: pos_x
576
+ let s: pos_y = a: pos_y
519
577
520
578
let s: can_accept = v: true
521
579
let l: has_info = v: false
522
580
523
581
if s: can_accept && v: shell_error
524
- if ! self . is_auto
582
+ if ! a: is_auto
525
583
call add (s: content , " <| curl error: is the server on? |>" )
526
584
endif
527
585
let s: can_accept = v: false
@@ -642,7 +700,9 @@ function! s:fim_on_stdout(job_id, data, event) dict
642
700
" display virtual text with the suggestion
643
701
let l: bufnr = bufnr (' %' )
644
702
645
- let l: id_vt_fim = nvim_create_namespace (' vt_fim' )
703
+ if s: ghost_text_nvim
704
+ let l: id_vt_fim = nvim_create_namespace (' vt_fim' )
705
+ endif
646
706
647
707
" construct the info message
648
708
if g: llama_config .show_info > 0 && l: has_info
@@ -671,15 +731,41 @@ function! s:fim_on_stdout(job_id, data, event) dict
671
731
endif
672
732
673
733
" display the suggestion and append the info to the end of the first line
674
- call nvim_buf_set_extmark (l: bufnr , l: id_vt_fim , s: pos_y - 1 , s: pos_x - 1 , {
675
- \ ' virt_text' : [[s: content [0 ], ' llama_hl_hint' ], [l: info , ' llama_hl_info' ]],
676
- \ ' virt_text_win_col' : virtcol (' .' ) - 1
677
- \ })
734
+ if s: ghost_text_nvim
735
+ call nvim_buf_set_extmark (l: bufnr , l: id_vt_fim , s: pos_y - 1 , s: pos_x - 1 , {
736
+ \ ' virt_text' : [[s: content [0 ], ' llama_hl_hint' ], [l: info , ' llama_hl_info' ]],
737
+ \ ' virt_text_win_col' : virtcol (' .' ) - 1
738
+ \ })
678
739
679
- call nvim_buf_set_extmark (l: bufnr , l: id_vt_fim , s: pos_y - 1 , 0 , {
680
- \ ' virt_lines' : map (s: content [1 :], {idx, val - > [[val, ' llama_hl_hint' ]]}),
681
- \ ' virt_text_win_col' : virtcol (' .' )
682
- \ })
740
+ call nvim_buf_set_extmark (l: bufnr , l: id_vt_fim , s: pos_y - 1 , 0 , {
741
+ \ ' virt_lines' : map (s: content [1 :], {idx, val - > [[val, ' llama_hl_hint' ]]}),
742
+ \ ' virt_text_win_col' : virtcol (' .' )
743
+ \ })
744
+ elseif s: ghost_text_vim
745
+ let l: new_suffix = s: content [0 ]
746
+ if ! empty (l: new_suffix )
747
+ call prop_add (s: pos_y , s: pos_x + 1 , {
748
+ \ ' type' : s: hlgroup_hint ,
749
+ \ ' text' : l: new_suffix
750
+ \ })
751
+ endif
752
+ for line in s: content [1 :]
753
+ call prop_add (s: pos_y , 0 , {
754
+ \ ' type' : s: hlgroup_hint ,
755
+ \ ' text' : line ,
756
+ \ ' text_padding_left' : s: get_indent (line ),
757
+ \ ' text_align' : ' below'
758
+ \ })
759
+ endfor
760
+ if ! empty (l: info )
761
+ call prop_add (s: pos_y , 0 , {
762
+ \ ' type' : s: hlgroup_info ,
763
+ \ ' text' : l: info ,
764
+ \ ' text_padding_left' : col (' $' ),
765
+ \ ' text_wrap' : ' truncate'
766
+ \ })
767
+ endif
768
+ endif
683
769
684
770
" setup accept shortcuts
685
771
inoremap <buffer> <Tab> <C-O> :call llama#fim_accept(v:false)<CR>
@@ -688,7 +774,7 @@ function! s:fim_on_stdout(job_id, data, event) dict
688
774
let s: hint_shown = v: true
689
775
endfunction
690
776
691
- function ! s: fim_on_exit (job_id, exit_code, event ) dict
777
+ function ! s: fim_on_exit (job_id, exit_code, event = v: null )
692
778
if a: exit_code != 0
693
779
echom " Job failed with exit code: " . a: exit_code
694
780
endif
0 commit comments