diff --git a/.gitmodules b/.gitmodules index 6cbadcbda9..87c1c6b385 100644 --- a/.gitmodules +++ b/.gitmodules @@ -887,6 +887,9 @@ [submodule "vendor/grammars/language-typelanguage"] path = vendor/grammars/language-typelanguage url = https://github.com/goodmind/language-typelanguage +[submodule "vendor/grammars/language-vim9"] + path = vendor/grammars/language-vim9 + url = https://github.com/DanBradbury/language-vim9.git [submodule "vendor/grammars/language-viml"] path = vendor/grammars/language-viml url = https://github.com/Alhadis/language-viml diff --git a/grammars.yml b/grammars.yml index 13979f0c14..4101c8a713 100644 --- a/grammars.yml +++ b/grammars.yml @@ -845,6 +845,8 @@ vendor/grammars/language-turing: - source.turing vendor/grammars/language-typelanguage: - source.tl +vendor/grammars/language-vim9: +- source.vim vendor/grammars/language-viml: - source.vim-snippet - source.viml diff --git a/lib/linguist/heuristics.yml b/lib/linguist/heuristics.yml index 17d6802916..a09a745036 100644 --- a/lib/linguist/heuristics.yml +++ b/lib/linguist/heuristics.yml @@ -1025,6 +1025,11 @@ disambiguations: pattern: '\A##fileformat=VCF' - language: vCard pattern: '\ABEGIN:VCARD' +- extensions: ['.vim'] + rules: + - language: Vim9 script + pattern: '^\s*vim9script(?:\s+noclear)?\s*$' + - language: Vim script - extensions: ['.w'] rules: - language: OpenEdge ABL diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 7ea73879c5..00e0b7a160 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -8277,6 +8277,18 @@ Vim Snippet: tm_scope: source.vim-snippet ace_mode: text language_id: 81265970 +Vim9 Script: + type: programming + color: "#007030" + tm_scope: source.vim + aliases: + - vim9 + - vim9script + extensions: + - ".vim" + - ".vim9" + ace_mode: text + language_id: 534204182 Visual Basic .NET: type: programming color: "#945db7" diff --git a/samples/Vim9 Script/advanced.vim b/samples/Vim9 Script/advanced.vim new file mode 100644 index 0000000000..b92495cfb2 --- /dev/null +++ b/samples/Vim9 Script/advanced.vim @@ -0,0 +1,185 @@ +vim9script noclear +# Advanced Vim9 Script Example +# Demonstrates modern Vim9 features for the Linguist repository + +# Strict typing and namespace +import autoload 'knack.vim' as knack +import autoload './utils.vim' as utils + +# Constants and typed variables +const BUFFER_SIZE: number = 1024 +const MAX_RETRIES: number = 3 +const CONFIG_DIR: string = $HOME .. '/.config/vim' + +# Class definitions (Vim9 feature) +class Cache + var entries: dict = {} + var max_size: number + + def new(size: number = 100) + this.max_size = size + enddef + + def set(key: string, value: any): void + if len(this.entries) >= this.max_size + var first_key = keys(this.entries)[0] + unlet this.entries[first_key] + endif + this.entries[key] = value + enddef + + def get(key: string): any + return get(this.entries, key, null) + enddef + + def clear(): void + this.entries = {} + enddef +endclass + +# Modern function syntax with type annotations +def ProcessBuffer(buf: number, opts: dict): dict + var result: dict = {} + var lines: list = getbufline(buf, 1, '$') + + # Process with early returns + if empty(lines) + result['status'] = 'empty' + result['count'] = '0' + return result + endif + + var count: number = 0 + for line in lines + if line =~# '^\s*#.*autocmd' + count += 1 + endif + endfor + + result['status'] = 'success' + result['count'] = string(count) + result['size'] = string(len(lines)) + + return result +enddef + +# Lambda functions with strict typing +var multiply: func(number, number): number = (a, b) => a * b +var filter_nums: func(list): list = + (nums) => nums->filter('v:val > 10') + +# Object methods and method chaining +def ApplyFormatting(content: string): string + return content + ->substitute(' ', ' ', 'g') + ->trim() + ->tolower() +enddef + +# Enumerate with type safety +def AnalyzeSettings(settings: dict): list> + var results: list> = [] + + for [key, value] in items(settings) + var item: dict = { + 'key': key, + 'value': value, + 'type': typename(value), + 'length': strlen(value) + } + results->add(item) + endfor + + return results +enddef + +# Recursive function example +def Factorial(n: number): number + if n <= 1 + return 1 + else + return n * Factorial(n - 1) + endif +enddef + +# Error handling with try-catch +def SafeFileRead(filepath: string): list + var lines: list = [] + + try + lines = readfile(filepath) + catch /E484/ + echomsg 'File not found: ' .. filepath + lines = [] + catch + echomsg 'Error reading file: ' .. v:exception + lines = [] + endtry + + return lines +enddef + +# Variadic function +def JoinPaths(...parts: list): string + return parts->join('/') +enddef + +# Command definitions using new syntax +command! -nargs=? -complete=file MyCommand { + var arg = + var result = ProcessBuffer(bufnr(), {'name': arg}) + echomsg 'Result: ' .. string(result) +} + +# Augroup with modern syntax +augroup MyAutocmds + autocmd! + autocmd BufWritePost *.vim { + var buf = expand('') + echomsg 'Wrote buffer ' .. buf + } + autocmd FileType python { + setlocal shiftwidth=4 + setlocal tabstop=4 + } +augroup END + +# Using the cache class +var cache: Cache = Cache.new(50) +cache.set('user_prefs', {'theme': 'dark', 'font_size': 12}) + +def GetUserPrefs(): any + var prefs = cache.get('user_prefs') + if prefs == null + prefs = {'theme': 'light', 'font_size': 10} + cache.set('user_prefs', prefs) + endif + return prefs +enddef + +# Advanced list comprehension patterns +var numbers: list = range(1, 100) +var evens: list = numbers->filter('v:val % 2 == 0') +var squares: list = numbers->map('v:val * v:val') + +# String interpolation (Vim9) +def FormatMessage(name: string, count: number): string + return $'Found {count} items for {name}' +enddef + +# Abort on first error (Vim9 behavior by default with vim9script) +if !exists('g:loaded_myplugin') + finish +endif + +g:loaded_myPlugin = 1 + +# Export public interface +export def PublicAPI(input: string): string + return 'Processed: ' .. input +enddef + +# Internal helper (not exported) +def PrivateHelper(): void + echomsg 'This is internal' +enddef diff --git a/samples/Vim9 Script/basic.vim b/samples/Vim9 Script/basic.vim new file mode 100644 index 0000000000..2786c74840 --- /dev/null +++ b/samples/Vim9 Script/basic.vim @@ -0,0 +1,57 @@ +vim9script + +var myNumber: number = 42 +const PI: number = 3.14159 +final myList: list = ['vim', 'neovim', 'vim9'] + +def HelloWorld(): void + echo "Hello, Vim9!" +enddef + +def Add(a: number, b: number): number + return a + b +enddef + +def ProcessList(items: list): dict + var result: dict = {} + for item in items + result[item] = len(item) + endfor + return result +enddef + +var double = (n: number): number => n * 2 +var squared = (n: number): number => n * n + +def CheckValue(val: number): string + if val > 0 + return "positive" + elseif val < 0 + return "negative" + else + return "zero" + endif +enddef + +var name = "Vim9" +echo $"Welcome to {name}!" + +export def PublicFunction(): void + echo "This function is exported" +enddef + +def SafeDivide(a: number, b: number): number + try + return a / b + catch + echoerr "Division error" + return 0 + endtry +enddef + +var isEnabled: bool = true +var isDisabled: bool = false + +HelloWorld() +var sum = Add(10, 20) +echo $"Sum: {sum}" diff --git a/samples/Vim9 Script/copilot-chat.vim b/samples/Vim9 Script/copilot-chat.vim new file mode 100644 index 0000000000..b12985f9dd --- /dev/null +++ b/samples/Vim9 Script/copilot-chat.vim @@ -0,0 +1,115 @@ +vim9script +scriptencoding utf-8 + +import autoload 'copilot_chat/auth.vim' as auth +import autoload 'copilot_chat/buffer.vim' as _buffer +import autoload 'copilot_chat/api.vim' as api + +export def OpenChat(): void + if _buffer.HasActiveChat() && g:copilot_reuse_active_chat == 1 + _buffer.FocusActiveChat() + else + _buffer.Create() + endif + timer_start(10, (_) => auth.VerifySignin()) +enddef + +export def StartChat(message: string): void + OpenChat() + _buffer.AppendMessage(message) + api.AsyncRequest([{'content': message, 'role': 'user'}], []) +enddef + +export def ResetChat(): void + if g:copilot_chat_active_buffer == -1 || !bufexists(g:copilot_chat_active_buffer) + echom 'No active chat window to reset' + return + endif + + var current_buf = bufnr('%') + + # Switch to the active chat buffer if not already there + if current_buf != g:copilot_chat_active_buffer + execute 'buffer ' .. g:copilot_chat_active_buffer + endif + + deletebufline('%', 1, '$') + + _buffer.WelcomeMessage() + + if current_buf != g:copilot_chat_active_buffer && bufexists(current_buf) + execute 'buffer ' .. current_buf + endif +enddef + +export def SubmitMessage(): void + auth.GetTokens() + var messages = [] + var pattern = ' ━\+$' + var all_file_lists = [] + cursor(1, 1) + + while search(pattern, 'W') > 0 + var header_line = getline('.') + var role = 'user' + # Check separator icon to determine message role + # Separator with  icon indicates assistant response, otherwise user message + if stridx(header_line, ' ') != -1 + role = 'assistant' + endif + var start_line: number = line('.') + 1 + var end_line: number = search(pattern, 'W') + if end_line == 0 + end_line = line('$') + else + end_line -= 1 + cursor(line('.') - 1, col('.')) + endif + + var lines: list = getline(start_line, end_line) + var file_list: list = [] + + for i in range(len(lines)) + var line: string = lines[i] + if line =~? '^> \(\w\+\)' + var text: string = matchstr(line, '^> \(\w\+\)') + text = substitute(text, '^> ', '', '') + if has_key(g:copilot_chat_prompts, text) + lines[i] = g:copilot_chat_prompts[text] + endif + elseif line =~? '^#file: ' + var filename: string = matchstr(line, '^#file: \s*\zs.*\ze$') + add(file_list, filename) + endif + endfor + var message: string = join(lines, "\n") + + add(messages, {'content': message, 'role': role}) + add(all_file_lists, file_list) + cursor(line('.'), col('.') + 1) + endwhile + + # Limit message history to improve performance + # Only send the most recent messages based on configuration + var limit: number = g:copilot_chat_message_history_limit + if len(messages) > limit && limit > 0 + var start_idx: number = len(messages) - limit + messages = messages[start_idx : ] + all_file_lists = all_file_lists[start_idx : ] + endif + + # Consolidate file lists from recent messages + # O(n) consolidation using dictionary for O(1) duplicate detection (improved from O(n²)) + var consolidated_files: list = [] + var seen: dict = {} + for files in all_file_lists + for file in files + if !has_key(seen, file) + seen[file] = 1 + add(consolidated_files, file) + endif + endfor + endfor + + api.AsyncRequest(messages, consolidated_files) +enddef diff --git a/test/fixtures/Vim Script/legacy.vim b/test/fixtures/Vim Script/legacy.vim new file mode 100644 index 0000000000..35eede43b3 --- /dev/null +++ b/test/fixtures/Vim Script/legacy.vim @@ -0,0 +1,5 @@ +let s:a = 123 + +function s:foo() + return s:a +endfunction diff --git a/test/fixtures/Vim9 Script/vim9script.vim b/test/fixtures/Vim9 Script/vim9script.vim new file mode 100644 index 0000000000..21b43a111b --- /dev/null +++ b/test/fixtures/Vim9 Script/vim9script.vim @@ -0,0 +1,3 @@ +vim9script noclear + +var a = 123 diff --git a/test/test_heuristics.rb b/test/test_heuristics.rb index 506a2d1d7f..4025cbac8e 100755 --- a/test/test_heuristics.rb +++ b/test/test_heuristics.rb @@ -1179,6 +1179,13 @@ def test_vcf_by_heuristics }) end + def test_vim_by_heuristics + assert_heuristics({ + "Vim9 Script" => all_fixtures("Vim9 Script", "*.vim"), + "Vim Script" => all_fixtures("Vim Script", "*.vim") + }) + end + def test_w_by_heuristics assert_heuristics({ "CWeb" => all_fixtures("CWeb", "*.w"), diff --git a/vendor/README.md b/vendor/README.md index da47065d96..1a43cf593c 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -664,6 +664,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Vim Help File:** [Alhadis/language-viml](https://github.com/Alhadis/language-viml) - **Vim Script:** [Alhadis/language-viml](https://github.com/Alhadis/language-viml) - **Vim Snippet:** [Alhadis/language-viml](https://github.com/Alhadis/language-viml) +- **Vim9 Script:** [DanBradbury/language-vim9](https://github.com/DanBradbury/language-vim9) - **Visual Basic .NET:** [peters-ben-0007/VBDotNetSyntax](https://github.com/peters-ben-0007/VBDotNetSyntax) - **Visual Basic 6.0:** [serkonda7/vscode-vba](https://github.com/serkonda7/vscode-vba) - **Volt:** [textmate/d.tmbundle](https://github.com/textmate/d.tmbundle) diff --git a/vendor/grammars/language-vim9 b/vendor/grammars/language-vim9 new file mode 160000 index 0000000000..28d3207e56 --- /dev/null +++ b/vendor/grammars/language-vim9 @@ -0,0 +1 @@ +Subproject commit 28d3207e56dc05a5e25ba92bc975b9fc3a39ca79