aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/cmd/tools/vcomplete.v
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/cmd/tools/vcomplete.v')
-rw-r--r--v_windows/v/cmd/tools/vcomplete.v452
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)
+}