I'm trying to create a neovim color picker plugin, I made a Python application for selecting color from a color wheel using Python's tkinter library, and used a vimscript function to print the output of the Python application in nvim's buffer window.
But the problem is when I visually select buffer in nvim and try to replace with the output of Python application, It appends the output in buffer window instead of replacing it.
How can I fix it?
Here is the vimscript function which I've tried:
function! s:open_color_picker()
let lnum = line('.')
let col = col('.')
let current_line = getline(lnum)
let is_visual_mode = (mode() ==# 'v' || mode() ==# 'V' || mode() ==# "\<C-v>")
let insert_after = get(g:, 'NVIMColorPicker#InsertAfter#TheCursor', 0)
let insert_before = get(g:, 'NVIMColorPicker#InsertBefore#TheCursor', 0)
let color_picker_path = system('find ~/.local/share/nvim -type f -name color_picker.py')
let color_picker_path = substitute(color_picker_path, '\n\+$', '', '')
let color_picker_path = substitute(color_picker_path, '^\\n\+', '', '')
let color = system('python3 ' . shellescape(color_picker_path))
let color = substitute(color, '\n\+$', '', '')
let color = substitute(color, '^\\n\+', '', '')
if insert_after
" insert after the cursor
let hex_color = substitute(current_line, '\%' . (col + 1) . 'c', "\\0" . color, '')
call setline(lnum, hex_color)
elseif insert_before
" insert before the cursor
let hex_color = strpart(current_line, 0, col - 1) . color . strpart(current_line, col - 1)
call setline(lnum, hex_color)
else
" default behavior (insert after the cursor)
let hex_color = substitute(current_line, '\%' . (col + 1) . 'c', "\\0" . color, '')
call setline(lnum, hex_color)
endif
" for replacing the visually selected hex
if is_visual_mode
let old_reg = getreg('"')
let old_regtype = getregtype('"')
normal! gvy
let selected_text = @"
let new_output = color
call setreg('"', new_output, 'v')
let start_pos = getpos("'<")
let end_pos = getpos("'>")
let start_line = start_pos[1]
let start_col = start_pos[2]
let end_line = end_pos[1]
let end_col = end_pos[2]
if start_line == end_line
let current_line = getline(start_line)
let new_line = current_line[:start_col-2] . new_output . current_line[end_col-1:]
call setline(start_line, new_line)
else
let first_line = getline(start_line)
let last_line = getline(end_line)
let new_first_line = first_line[:start_col-2] . new_output
let new_last_line = last_line[end_col-1:]
call setline(start_line, new_first_line)
call setline(end_line, new_last_line)
if end_line - start_line > 1
call deletebufline('%', start_line + 1, end_line - 1)
endif
endif
call setreg('"', old_reg, old_regtype)
endif
endfunction
command! -range ColorPicker call s:open_color_picker()
Your question is not clear on what you want to do if a visual selection is present. This answer assumes you want to replace the entire selection with whatever it is that your
color_picker.pyreturns.I chose to retire the global variable
g:NVIMColorPicker#Insert#After#TheCursor. There's really no point in having two variables that are mutually exclusive. Now, the default behavior is to insert after the cursor unlessg:NVIMColorPicker#InsertBefore#TheCursoris set to a truthy value.Another decision was to return a Vim command which can be run through
:normal!instead of changing the lines throughsetline()but that's subject to debate. My solution usesexecute 'normal!...a lot which is really something I like to avoid in general. One could debate about which approach is more pretty.Note that I used Vim's built-in function
findfile()instead of falling back to the externalfindcommand. For a finished plugin, I'd expect the paths to be reasonably defined. Knowing where it is is better than knowing where to search.The whole range-management is now done within the command
:ColorPickerwhich reliefss:open_color_picker()of the burden of handling visual ranges.Namely, if a
<range>was present, we restore the last visual selection withgv(we're already in command-line mode and not in visual mode anymore) and shove it into the black-hole register with"_d. Bye bye.The remark about command-line mode is by the way why your original function would not work. By the time we execute the function, visual mode is gone and
mode()will have forgotten all about it.After that, just execute whatever
s:open_color_picker()returns as normal mode commands.For reference, see the
:helpon the respective commands used.One last thing, I wrote and tested all this in Vim 9.1. It should work in NeoVim as well.