mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
v: add shell auto-completion with source <(v complete setup bash)
(#6886)
This commit is contained in:
parent
93d460f8fc
commit
82b16cbf45
302
cmd/tools/vcomplete.v
Normal file
302
cmd/tools/vcomplete.v
Normal file
@ -0,0 +1,302 @@
|
||||
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
// Utility functions helping integrate with various shell auto-completion systems.
|
||||
// The install process and communication is inspired from that of [kitty](https://sw.kovidgoyal.net/kitty/#completion-for-kitty)
|
||||
// This method avoids writing and maintaining external files on the user's file system.
|
||||
// The user will be responsible for adding a small line to their .*rc - that will ensure *live* (i.e. not-static)
|
||||
// auto-completion features.
|
||||
//
|
||||
// # bash
|
||||
// To install auto-completion for V in bash, simply add this code to your ~/.bashrc:
|
||||
// `source /dev/stdin <<<"$(v complete setup bash)"`
|
||||
// On more recent versions of bash (>3.2) this should suffice:
|
||||
// `source <(v complete setup bash)`
|
||||
//
|
||||
// # fish
|
||||
// TODO
|
||||
//
|
||||
// # zsh
|
||||
// TODO
|
||||
//
|
||||
// # powershell
|
||||
// TODO
|
||||
module main
|
||||
|
||||
import os
|
||||
|
||||
const (
|
||||
auto_complete_shells = ['bash', 'fish', 'zsh', 'powershell']
|
||||
)
|
||||
|
||||
// Snooped from cmd/v/v.v, vlib/v/pref/pref.v
|
||||
const (
|
||||
auto_complete_commands = [
|
||||
/* simple_cmd */
|
||||
'fmt',
|
||||
'up',
|
||||
'vet',
|
||||
'self',
|
||||
'tracev',
|
||||
'symlink',
|
||||
'bin2v',
|
||||
'test',
|
||||
'test-fmt',
|
||||
'test-compiler',
|
||||
'test-fixed',
|
||||
'test-cleancode',
|
||||
'repl',
|
||||
'complete',
|
||||
'build-tools',
|
||||
'build-examples',
|
||||
'build-vbinaries',
|
||||
'setup-freetype',
|
||||
'doc',
|
||||
'doctor',
|
||||
/* commands */
|
||||
'help',
|
||||
'new',
|
||||
'init',
|
||||
'complete',
|
||||
'translate',
|
||||
'self',
|
||||
'search',
|
||||
'install',
|
||||
'update',
|
||||
'upgrade',
|
||||
'outdated',
|
||||
'list',
|
||||
'remove',
|
||||
'vlib-docs',
|
||||
'get',
|
||||
'version',
|
||||
'run',
|
||||
'build',
|
||||
'build-module',
|
||||
]
|
||||
auto_complete_flags = [
|
||||
'-apk',
|
||||
'-show-timings',
|
||||
'-check-syntax',
|
||||
'-v',
|
||||
'-progress',
|
||||
'-silent',
|
||||
'-g',
|
||||
'-cg',
|
||||
'-repl',
|
||||
'-live',
|
||||
'-sharedlive',
|
||||
'-shared',
|
||||
'--enable-globals',
|
||||
'-enable-globals',
|
||||
'-autofree',
|
||||
'-compress',
|
||||
'-freestanding',
|
||||
'-no-preludes',
|
||||
'-prof',
|
||||
'-profile',
|
||||
'-profile-no-inline',
|
||||
'-prod',
|
||||
'-simulator',
|
||||
'-stats',
|
||||
'-obfuscate',
|
||||
'-translated',
|
||||
'-color',
|
||||
'-nocolor',
|
||||
'-showcc',
|
||||
'-show-c-output',
|
||||
'-experimental',
|
||||
'-usecache',
|
||||
'-prealloc',
|
||||
'-parallel',
|
||||
'-x64',
|
||||
'-W',
|
||||
'-reuse-tmpc',
|
||||
'-w',
|
||||
'-print_v_files',
|
||||
'-error-limit',
|
||||
'-os',
|
||||
'-printfn',
|
||||
'-cflags',
|
||||
'-define',
|
||||
'-d',
|
||||
'-cc',
|
||||
'-o',
|
||||
'-b',
|
||||
'-path',
|
||||
'-custom-prelude',
|
||||
'-name',
|
||||
'-bundle',
|
||||
'-V',
|
||||
'-version',
|
||||
'--version',
|
||||
]
|
||||
auto_complete_compilers = [
|
||||
'cc',
|
||||
'gcc',
|
||||
'tcc',
|
||||
'tinyc',
|
||||
'clang',
|
||||
'mingw',
|
||||
'msvc',
|
||||
]
|
||||
)
|
||||
|
||||
fn auto_complete(args []string) {
|
||||
if args.len <= 1 || args[0] != 'complete' {
|
||||
if args.len == 1 {
|
||||
eprintln('auto completion require arguments to work.')
|
||||
} else {
|
||||
eprintln('auto completion failed for "$args".')
|
||||
}
|
||||
exit(1)
|
||||
}
|
||||
sub := args[1]
|
||||
sub_args := args[1..]
|
||||
match sub {
|
||||
'setup' {
|
||||
if sub_args.len <= 1 || sub_args[1] !in auto_complete_shells {
|
||||
eprintln('please specify a shell to setup auto completion for ($auto_complete_shells).')
|
||||
exit(1)
|
||||
}
|
||||
shell := sub_args[1]
|
||||
mut setup := ''
|
||||
match shell {
|
||||
'bash' { setup = r'
|
||||
_v_completions() {
|
||||
local src
|
||||
local limit
|
||||
# Send all words up to the word the cursor is currently on
|
||||
let limit=1+$COMP_CWORD
|
||||
src=$(v complete bash $(printf "%s\n" ${COMP_WORDS[@]: 0:$limit}))
|
||||
if [[ $? == 0 ]]; then
|
||||
eval ${src}
|
||||
#echo ${src}
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o nospace -F _v_completions v
|
||||
' }
|
||||
// 'fish' {} //TODO see https://github.com/kovidgoyal/kitty/blob/75a94bcd96f74be024eb0a28de87ca9e15c4c995/kitty/complete.py#L113
|
||||
// 'zsh' {} //TODO see https://github.com/kovidgoyal/kitty/blob/75a94bcd96f74be024eb0a28de87ca9e15c4c995/kitty/complete.py#L87
|
||||
// 'powershell' {} //TODO
|
||||
else {}
|
||||
}
|
||||
println(setup)
|
||||
}
|
||||
'bash' {
|
||||
if sub_args.len <= 1 {
|
||||
exit(0)
|
||||
}
|
||||
mut lines := []string{}
|
||||
list := auto_complete_request(sub_args[1..])
|
||||
for entry in list {
|
||||
lines << "COMPREPLY+=(\'$entry\')"
|
||||
}
|
||||
println(lines.join('\n'))
|
||||
}
|
||||
// 'fish' {} //TODO
|
||||
// 'zsh' {} //TODO
|
||||
// 'powershell' {} //TODO
|
||||
else {}
|
||||
}
|
||||
exit(0)
|
||||
}
|
||||
|
||||
fn auto_complete_request(args []string) []string {
|
||||
// Using space will ensure a uniform input in cases where the shell
|
||||
// returns the completion input as a string
|
||||
split_by := ' '
|
||||
request := args.join(split_by)
|
||||
mut list := []string{}
|
||||
// new_part := request.ends_with('\n\n')
|
||||
mut parts := request.trim_right(' ').split(split_by)
|
||||
if parts.len <= 1 { // 'v <tab>' -> top level commands.
|
||||
for command in auto_complete_commands {
|
||||
list << command
|
||||
}
|
||||
} else {
|
||||
part := parts.last().trim(' ')
|
||||
if part.starts_with('-') { // 'v -<tab>' -> flags.
|
||||
for flag in auto_complete_flags {
|
||||
if flag == part {
|
||||
if flag == '-cc' { // 'v -cc <tab>' -> list of available compilers.
|
||||
for compiler in auto_complete_compilers {
|
||||
path := os.find_abs_path_of_executable(compiler) or {
|
||||
''
|
||||
}
|
||||
if path != '' {
|
||||
list << compiler
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if flag.starts_with(part) { // 'v -<char(s)><tab>' -> flags matching "<char(s)>".
|
||||
list << flag
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if part == 'help' { // 'v help <tab>' -> top level commands except "help".
|
||||
list = auto_complete_commands.filter(it != part).filter(it != 'complete')
|
||||
} else if part == 'build' { // 'v build <tab>' -> all flags.
|
||||
list = auto_complete_flags
|
||||
} else {
|
||||
// 'v <char(s)><tab>' -> commands matching "<char(s)>".
|
||||
// Don't include if part matches a full command - instead go to path completion below.
|
||||
for command in auto_complete_commands {
|
||||
if part != command && command.starts_with(part) {
|
||||
list << command
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Nothing of value was found.
|
||||
// Mimic shell dir and file completion
|
||||
if list.len == 0 {
|
||||
mut ls_path := '.'
|
||||
mut collect_all := part in auto_complete_commands
|
||||
mut path_complete := false
|
||||
if part.ends_with(os.path_separator) || part == '.' || part == '..' { // 'v <command>(.*/$|.|..)<tab>' -> output full directory list
|
||||
ls_path = '.' + os.path_separator + part
|
||||
collect_all = true
|
||||
} else if !collect_all && part.contains(os.path_separator) && os.is_dir(os.dir(part)) { // 'v <command>(.*/.* && os.is_dir)<tab>' -> output completion friendly directory list
|
||||
ls_path = os.dir(part)
|
||||
path_complete = true
|
||||
}
|
||||
entries := os.ls(ls_path) or {
|
||||
return list
|
||||
}
|
||||
last := part.all_after_last(os.path_separator)
|
||||
if path_complete {
|
||||
path := part.all_before_last(os.path_separator)
|
||||
for entry in entries {
|
||||
if entry.starts_with(last) {
|
||||
list << entry
|
||||
}
|
||||
}
|
||||
// If only one possible file - send full path to completion system.
|
||||
// Please note that this might be bash specific - needs more testing.
|
||||
if list.len == 1 {
|
||||
list = [os.join_path(path, list[0])]
|
||||
}
|
||||
} else {
|
||||
for entry in entries {
|
||||
if collect_all {
|
||||
list << entry
|
||||
} else {
|
||||
if entry.starts_with(last) {
|
||||
list << entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fn main() {
|
||||
args := os.args[1..]
|
||||
//println('"$args"')
|
||||
auto_complete(args)
|
||||
}
|
Loading…
Reference in New Issue
Block a user