diff options
Diffstat (limited to 'v_windows/v/cmd/tools/vcomplete.v')
-rw-r--r-- | v_windows/v/cmd/tools/vcomplete.v | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/v_windows/v/cmd/tools/vcomplete.v b/v_windows/v/cmd/tools/vcomplete.v new file mode 100644 index 0000000..5558382 --- /dev/null +++ b/v_windows/v/cmd/tools/vcomplete.v @@ -0,0 +1,452 @@ +// Copyright (c) 2019-2021 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 +// For versions of fish <3.0.0, add the following to your `~/.config/fish/config.fish` +// `v complete setup fish | source` +// Later versions of fish source completions by default. +// +// # zsh +// To install auto-completion for V in zsh - please add the following to your `~/.zshrc`: +// ``` +// autoload -Uz compinit +// compinit +// # Completion for v +// v complete setup zsh | source /dev/stdin +// ``` +// Please note that you should let v load the zsh completions after the call to compinit +// +// # powershell +// To install auto-complete for V in PowerShell, simply do this +// `v complete setup powershell >> $PROFILE` +// and reload profile +// `& $PROFILE` +// If `$PROFILE` didn't exist yet, create it before +// `New-Item -Type File -Force $PROFILE` +// +module main + +import os + +const ( + auto_complete_shells = ['bash', 'fish', 'zsh', 'powershell'] // list of supported shells + vexe = os.getenv('VEXE') +) + +// 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-self', + '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', + '-native', + '-W', + '-keepc', + '-w', + '-print-v-files', + '-error-limit', + '-message-limit', + '-os', + '-printfn', + '-cflags', + '-define', + '-d', + '-cc', + '-o', + '-b', + '-path', + '-custom-prelude', + '-name', + '-bundle', + '-V', + '-version', + '--version', + ] + auto_complete_flags_doc = [ + '-all', + '-f', + '-h', + '-help', + '-m', + '-o', + '-readme', + '-v', + '-filename', + '-pos', + '-no-timestamp', + '-inline-assets', + '-open', + '-p', + '-s', + '-l', + ] + auto_complete_flags_fmt = [ + '-c', + '-diff', + '-l', + '-w', + '-debug', + '-verify', + ] + auto_complete_flags_bin2v = [ + '-h', + '--help', + '-m', + '--module', + '-p', + '--prefix', + '-w', + '--write', + ] + auto_complete_flags_self = [ + '-prod', + ] + auto_complete_compilers = [ + 'cc', + 'gcc', + 'tcc', + 'tinyc', + 'clang', + 'mingw', + 'msvc', + ] +) + +// auto_complete prints auto completion results back to the calling shell's completion system. +// auto_complete acts as communication bridge between the calling shell and V's completions. +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 = ' +_v_completions() { + local src + local limit + # Send all words up to the word the cursor is currently on + let limit=1+\$COMP_CWORD + src=\$($vexe 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' { + setup = ' +function __v_completions + # Send all words up to the one before the cursor + $vexe complete fish (commandline -cop) +end +complete -f -c v -a "(__v_completions)" +' + } + 'zsh' { + setup = ' +#compdef v +_v() { + local src + # Send all words up to the word the cursor is currently on + src=\$($vexe complete zsh \$(printf "%s\\n" \${(@)words[1,\$CURRENT]})) + if [[ \$? == 0 ]]; then + eval \${src} + #echo \${src} + fi +} +compdef _v v +' + } + 'powershell' { + setup = ' +Register-ArgumentCompleter -Native -CommandName v -ScriptBlock { + param(\$commandName, \$wordToComplete, \$cursorPosition) + $vexe complete powershell "\$wordToComplete" | ForEach-Object { + [System.Management.Automation.CompletionResult]::new(\$_, \$_, \'ParameterValue\', \$_) + } +} +' + } + 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', 'powershell' { + if sub_args.len <= 1 { + exit(0) + } + mut lines := []string{} + list := auto_complete_request(sub_args[1..]) + for entry in list { + lines << '$entry' + } + println(lines.join('\n')) + } + 'zsh' { + if sub_args.len <= 1 { + exit(0) + } + mut lines := []string{} + list := auto_complete_request(sub_args[1..]) + for entry in list { + lines << 'compadd -U -S' + '""' + ' -- ' + "'$entry';" + } + println(lines.join('\n')) + } + else {} + } + exit(0) +} + +// append_separator_if_dir is a utility function.that returns the input `path` appended an +// OS dependant path separator if the `path` is a directory. +fn append_separator_if_dir(path string) string { + if os.is_dir(path) && !path.ends_with(os.path_separator) { + return path + os.path_separator + } + return path +} + +// auto_complete_request retuns a list of completions resolved from a full argument list. +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 (['v','run'] vs. ['v run']). + 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(' ') + mut parent_command := '' + for i := parts.len - 1; i >= 0; i-- { + if parts[i].starts_with('-') { + continue + } + parent_command = parts[i] + break + } + get_flags := fn (base []string, flag string) []string { + if flag.len == 1 { return base + } else { return base.filter(it.starts_with(flag)) + } + } + if part.starts_with('-') { // 'v -<tab>' -> flags. + match parent_command { + 'bin2v' { // 'v bin2v -<tab>' + list = get_flags(auto_complete_flags_bin2v, part) + } + 'build' { // 'v build -<tab>' -> flags. + list = get_flags(auto_complete_flags, part) + } + 'doc' { // 'v doc -<tab>' -> flags. + list = get_flags(auto_complete_flags_doc, part) + } + 'fmt' { // 'v fmt -<tab>' -> flags. + list = get_flags(auto_complete_flags_fmt, part) + } + 'self' { // 'v self -<tab>' -> flags. + list = get_flags(auto_complete_flags_self, part) + } + else { + 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 { + match part { + 'help' { // 'v help <tab>' -> top level commands except "help". + list = auto_complete_commands.filter(it != part && it != 'complete') + } + 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 << append_separator_if_dir(os.join_path(path, 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 = [list[0]] + } + } else { + for entry in entries { + if collect_all { + list << append_separator_if_dir(entry) + } else { + if entry.starts_with(last) { + list << append_separator_if_dir(entry) + } + } + } + } + } + } + return list +} + +fn main() { + args := os.args[1..] + // println('"$args"') + auto_complete(args) +} |