runtime(luau): runtime support is incomplete

Commit: 
https://github.com/vim/vim/commit/8181c6e31387c0467fcd6394d1b7da07f38e737c
Author: Lopy <[email protected]>
Date:   Thu Jun 18 19:36:32 2026 +0000

    runtime(luau): runtime support is incomplete
    
    Problem:  runtime(luau): runtime support is incomplete.
    Solution: Add Luau syntax, indent, filetype plugin and indent tests.
    
    closes: #20544
    
    Signed-off-by: Lopy <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/.github/MAINTAINERS b/.github/MAINTAINERS
index 6a786bfb1..f0266e772 100644
--- a/.github/MAINTAINERS
+++ b/.github/MAINTAINERS
@@ -255,6 +255,7 @@ runtime/ftplugin/liquid.vim                         @tpope
 runtime/ftplugin/logtalk.dict                          @pmoura
 runtime/ftplugin/logtalk.vim                           @pmoura
 runtime/ftplugin/lua.vim                               @dkearns
+runtime/ftplugin/luau.vim                              @lopi-py
 runtime/ftplugin/lynx.vim                              @dkearns
 runtime/ftplugin/m17ndb.vim                            @dseomn
 runtime/ftplugin/m3build.vim                           @dkearns
@@ -420,6 +421,7 @@ runtime/indent/less.vim                                     
@genoma
 runtime/indent/liquid.vim                              @tpope
 runtime/indent/logtalk.vim                             @pmoura
 runtime/indent/lua.vim                                 @marcuscf
+runtime/indent/luau.vim                                        @lopi-py
 runtime/indent/m17ndb.vim                              @dseomn
 runtime/indent/make.vim                                        @dkearns
 runtime/indent/meson.vim                               @Liambeguin
@@ -609,6 +611,7 @@ runtime/syntax/liquid.vim                           @tpope
 runtime/syntax/log.vim                                 @mao-yining
 runtime/syntax/logtalk.vim                             @pmoura
 runtime/syntax/lua.vim                                 @marcuscf
+runtime/syntax/luau.vim                                        @lopi-py
 runtime/syntax/lynx.vim                                        @dkearns
 runtime/syntax/lyrics.vim                              @ObserverOfTime
 runtime/syntax/m17ndb.vim                              @dseomn
diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt
index 61c72fffa..73ce826c1 100644
--- a/runtime/doc/filetype.txt
+++ b/runtime/doc/filetype.txt
@@ -1,4 +1,4 @@
-*filetype.txt* For Vim version 9.2.  Last change: 2026 Jun 17
+*filetype.txt* For Vim version 9.2.  Last change: 2026 Jun 18
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -890,6 +890,12 @@ For example, to use Lua 5.1, set the variables like this: >
        let g:lua_version = 5
        let g:lua_subversion = 1
 <
+LUAU                                   *ft-luau-plugin* *g:luau_folding*
+
+You can enable folding of Luau block constructs using |fold-expr| by: >
+
+       let g:luau_folding = 1
+<
 MAIL                                                   *ft-mail-plugin*
 
 Options:
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 40161f75c..1b9575fd9 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -7634,6 +7634,7 @@ ft-log-syntax     syntax.txt      /*ft-log-syntax*
 ft-lpc-syntax  syntax.txt      /*ft-lpc-syntax*
 ft-lua-plugin  filetype.txt    /*ft-lua-plugin*
 ft-lua-syntax  syntax.txt      /*ft-lua-syntax*
+ft-luau-plugin filetype.txt    /*ft-luau-plugin*
 ft-mail-plugin filetype.txt    /*ft-mail-plugin*
 ft-mail.vim    syntax.txt      /*ft-mail.vim*
 ft-make-syntax syntax.txt      /*ft-make-syntax*
@@ -7898,6 +7899,7 @@ g:lf_shell_syntax syntax.txt      /*g:lf_shell_syntax*
 g:lua_folding  filetype.txt    /*g:lua_folding*
 g:lua_subversion       filetype.txt    /*g:lua_subversion*
 g:lua_version  filetype.txt    /*g:lua_version*
+g:luau_folding filetype.txt    /*g:luau_folding*
 g:markdown_fenced_languages    syntax.txt      /*g:markdown_fenced_languages*
 g:markdown_minlines    syntax.txt      /*g:markdown_minlines*
 g:markdown_syntax_conceal      syntax.txt      /*g:markdown_syntax_conceal*
diff --git a/runtime/ftplugin/luau.vim b/runtime/ftplugin/luau.vim
index 458d0b05a..9b413e677 100644
--- a/runtime/ftplugin/luau.vim
+++ b/runtime/ftplugin/luau.vim
@@ -1,14 +1,193 @@
 " Vim filetype plugin file
-" Language:    Luau
-" Maintainer:  None yet
-" Last Change: 2023 Apr 30
+" Language:     Luau
+" Maintainer:   Lopy (@lopi-py)
+" Last Change:  2026 Jun 16
 
 if exists("b:did_ftplugin")
   finish
 endif
+let b:did_ftplugin = 1
 
-" Luau is a superset of Lua
-runtime! ftplugin/lua.vim
+let s:cpo_save = &cpo
+set cpo&vim
 
+setlocal comments=:---,:--
+setlocal commentstring=--\ %s
+setlocal formatoptions-=t formatoptions+=croql
 
-" vim: nowrap sw=2 sts=2 ts=8
+let &l:define = 
'\<\%(function\|local\%(\s\+function\)\=\|const\%(\s\+function\)\=\|type\%(\s\+function\)\=\|export\s\+type\%(\s\+function\)\=\|class\|declare\s\+\%(function\|class\|extern\s\+type\)\)\>'
+let &l:include = '\<require\s*('
+setlocal includeexpr=s:LuauInclude(v:fname)
+setlocal suffixesadd=.luau,.lua
+
+let b:undo_ftplugin = "setl cms< com< def< fo< inc< inex< sua<"
+
+let s:attr = '@\%(\h\w*\|\[[^]]*\]\)\s*'
+let s:line_end = '\s*\%(--.*\)\=$'
+let s:end_pat = '^\s*end\>' .. s:line_end
+let s:end_block = '^\s*\%(' .. s:attr .. '\)*if\>.\{-}\<then\>' ..
+      \ '\|^\s*\%(' .. s:attr .. '\)*class\>\s\+\h' ..
+      \ '\|^\s*\%(' .. s:attr .. '\)*declare\s\+class\>\s\+\h' ..
+      \ '\|^\s*\%(' .. s:attr .. '\)*declare\s\+extern\s\+type\>.\{-}\<with\>' 
.. s:line_end
+
+if exists("loaded_matchit") && !exists("b:match_words")
+  let b:match_ignorecase = 0
+  let s:match_tail = ':\<return\>\|^\s*\%(else\|elseif\)\>:' .. s:end_pat ..
+        \ ',^\s*repeat\>:\<until\>,' ..
+        \ '\%(--\)\=\[\(=*\)\[:\] \]'
+  let s:match_words = '\<\%(do\|function\)\>\|' .. s:end_block .. s:match_tail
+  let s:match_words_no_function = '\<do\>\|' .. s:end_block .. s:match_tail
+  let b:match_words = "LuauMatchWords()"
+  let b:match_skip = 'LuauMatchSkip()'
+  let b:undo_ftplugin ..= " | unlet! b:match_words b:match_ignorecase 
b:match_skip"
+endif
+
+if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
+  let b:browsefilter = "Luau Source Files (*.luau)     *.luau
"
+  if has("win32")
+    let b:browsefilter ..= "All Files (*.*)    *
"
+  else
+    let b:browsefilter ..= "All Files (*)      *
"
+  endif
+  let b:undo_ftplugin ..= " | unlet! b:browsefilter"
+endif
+
+if has("folding") && get(g:, "luau_folding", 0)
+  setlocal foldmethod=expr
+  setlocal foldexpr=s:LuauFold()
+  let b:luau_lasttick = -1
+  let b:undo_ftplugin ..= " | setl foldexpr< foldmethod< | unlet! 
b:luau_lasttick b:luau_foldlists"
+endif
+
+" the rest of the file needs to be sourced only once per Vim session
+if exists("s:loaded_luau") || &cp
+  let &cpo = s:cpo_save
+  unlet s:cpo_save
+  finish
+endif
+let s:loaded_luau = 1
+
+function s:LuauInclude(fname) abort
+  let fname = a:fname
+  if fname =~# '^@'
+    return fname
+  endif
+  for path in [fname, fname .. ".luau", fname .. ".lua", fname .. 
"/init.luau", fname .. "/init.lua"]
+    if filereadable(path)
+      return path
+    endif
+  endfor
+  return fname
+endfunction
+
+function s:IsStringOrComment(lnum, col) abort
+  let name = synIDattr(synID(a:lnum, a:col, 1), "name")
+  return name =~# '^luau\%(Comment\|.*String\)'
+endfunction
+
+function s:LineCommentStart(lnum) abort
+  let line = getline(a:lnum)
+  let midx = stridx(line, '--')
+  while midx != -1
+    if !s:IsStringOrComment(a:lnum, midx + 1)
+      return midx
+    endif
+    let midx = stridx(line, '--', midx + 2)
+  endwhile
+  return -1
+endfunction
+
+function s:InDeclareClass(lnum) abort
+  let save_cursor = getcurpos()
+  call cursor(a:lnum - 1, 1)
+  let lnum = search('^\s*\%(end\>\|declare\s\+class\>\)', 'bcnW')
+  call setpos('.', save_cursor)
+  return lnum > 0 && getline(lnum) =~# '^\s*declare\s\+class\>'
+endfunction
+
+function LuauMatchSkip() abort
+  let lnum = line(".")
+  let col = col(".")
+  let comment = s:LineCommentStart(lnum)
+  return s:IsStringOrComment(lnum, col)
+        \ || (comment != -1 && col > comment)
+        \ || (expand("<cword>") ==# "function" && s:InDeclareClass(lnum))
+endfunction
+
+function LuauMatchWords() abort
+  if exists("s:match_words_no_function")
+        \ && expand("<cword>") ==# "function" && s:InDeclareClass(line("."))
+    return s:match_words_no_function
+  endif
+  return get(s:, "match_words", "")
+endfunction
+
+let s:fold_guard = '^\s*\%(@\|\%(do\|if\|repeat\|for\|while\|public' ..
+      \ '\|function\|return\|local\|const\|type\|export\|class' ..
+      \ '\|declare\|end\|until\)\>\)'
+
+let s:patterns = [
+      \ ['^\s*do\>' .. s:line_end, s:end_pat, 'block'],
+      \ ['^\s*if\>.\{-}\<then\>' .. s:line_end, s:end_pat, 'block'],
+      \ ['^\s*repeat\>' .. s:line_end, '^\s*until\>.*', 'block'],
+      \ ['^\s*for\>.\{-}\<do\>' .. s:line_end, s:end_pat, 'block'],
+      \ ['^\s*while\>.\{-}\<do\>' .. s:line_end, s:end_pat, 'block'],
+      \ ['^\s*\%(' .. s:attr .. '\)*\%(public\s\+\)\=function\>.*', s:end_pat, 
'function'],
+      \ ['^\s*return\s\+function\>.*', s:end_pat, 'function'],
+      \ ['^\s*\%(' .. s:attr .. '\)*local\s\+function\>.*', s:end_pat, 
'function'],
+      \ ['^\s*\%(' .. s:attr .. '\)*const\s\+function\>.*', s:end_pat, 
'function'],
+      \ ['^\s*\%(' .. s:attr .. '\)*\%(export\s\+\)\=type\s\+function\>.*', 
s:end_pat, 'function'],
+      \ ['^\s*class\>\s\+\h.*', s:end_pat, 'class'],
+      \ ['^\s*declare\s\+class\>\s\+\h.*', s:end_pat, 'declare_class'],
+      \ ['^\s*declare\s\+extern\s\+type\>.\{-}\<with\>' .. s:line_end, 
s:end_pat, 'block'],
+      \ ]
+
+function s:LuauFold() abort
+  if b:luau_lasttick == b:changedtick
+    return b:luau_foldlists[v:lnum - 1]
+  endif
+  let b:luau_lasttick = b:changedtick
+
+  let b:luau_foldlists = []
+  let foldlist = []
+  let buf = getline(1, "$")
+  for line in buf
+    let open = 0
+    let end = 0
+    if line !~# s:fold_guard
+      call add(b:luau_foldlists, "" .. len(foldlist))
+      continue
+    endif
+    for t in s:patterns
+      let tagopen = t[0]
+      let tagend = t[1]
+      if line =~# tagopen
+        if t[2] ==# 'function' && len(foldlist) > 0 && foldlist[-1][2] ==# 
'declare_class'
+          continue
+        endif
+        call add(foldlist, t)
+        let open = 1
+        break
+      elseif line =~# tagend
+        if len(foldlist) > 0 && line =~# foldlist[-1][1]
+          call remove(foldlist, -1)
+          let end = 1
+        else
+          let foldlist = []
+        endif
+        break
+      endif
+    endfor
+    let prefix = ""
+    if open == 1 | let prefix = ">" | endif
+    if end == 1 | let prefix = "<" | endif
+    call add(b:luau_foldlists, prefix .. (len(foldlist) + end))
+  endfor
+
+  return b:luau_foldlists[v:lnum - 1]
+endfunction
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim: nowrap sw=2 sts=2 ts=8 noet:
diff --git a/runtime/indent/luau.vim b/runtime/indent/luau.vim
index 69893f739..a7422acf3 100644
--- a/runtime/indent/luau.vim
+++ b/runtime/indent/luau.vim
@@ -1,14 +1,153 @@
-" Vim filetype indent file
-" Language:    Luau
-" Maintainer:  None yet
-" Last Change: 2023 Apr 30
+" Vim indent file
+" Language:     Luau
+" Maintainer:   Lopy (@lopi-py)
+" Last Change:  2026 Jun 17
 
-" Only load this indent file when no other was loaded.
+" only load this indent file when no other was loaded
 if exists("b:did_indent")
-    finish
+  finish
 endif
+let b:did_indent = 1
 
-" Luau is a superset of Lua
-runtime! indent/lua.vim
+let s:cpo_save = &cpo
+set cpo&vim
 
+setlocal indentexpr=GetLuauIndent()
 
+" make Vim call GetLuauIndent() when it finds a closing keyword or delimiter
+setlocal indentkeys+=0=end,0=until,0=else,0=elseif,0=},0=),0=]
+
+setlocal autoindent
+
+let b:undo_indent = "setlocal autoindent< indentexpr< indentkeys<"
+
+" only define the function once
+if exists("*GetLuauIndent")
+  let &cpo = s:cpo_save
+  unlet s:cpo_save
+  finish
+endif
+
+function GetLuauIndent() abort
+  let ignorecase_save = &ignorecase
+  try
+    let &ignorecase = 0
+    return s:GetLuauIndentIntern()
+  finally
+    let &ignorecase = ignorecase_save
+  endtry
+endfunction
+
+function s:InDeclareClass(lnum) abort
+  let save_cursor = getcurpos()
+  call cursor(a:lnum - 1, 1)
+  let lnum = search('^\s*\%(end\>\|declare\s\+class\>\)', 'bcnW')
+  call setpos('.', save_cursor)
+  return lnum > 0 && getline(lnum) =~# '^\s*declare\s\+class\>'
+endfunction
+
+function s:IsStringOrComment(lnum, col) abort
+  let name = synIDattr(synID(a:lnum, a:col, 1), "name")
+  return name =~# '^luau\%(Comment\|.*String\)'
+endfunction
+
+function s:LineCommentStart(lnum) abort
+  let line = getline(a:lnum)
+  let midx = stridx(line, '--')
+  while midx != -1
+    if !s:IsStringOrComment(a:lnum, midx + 1)
+      return midx
+    endif
+    let midx = stridx(line, '--', midx + 2)
+  endwhile
+  return -1
+endfunction
+
+function s:IsCode(lnum, col) abort
+  let comment = s:LineCommentStart(a:lnum)
+  return (comment == -1 || a:col <= comment) && !s:IsStringOrComment(a:lnum, 
a:col)
+endfunction
+
+function s:HasBlockCloser(lnum) abort
+  let line = getline(a:lnum)
+  let midx = match(line, '\<\%(end\|until\)\>')
+  while midx != -1
+    if s:IsCode(a:lnum, midx + 1)
+      return 1
+    endif
+    let midx = match(line, '\<\%(end\|until\)\>', midx + 1)
+  endwhile
+  return 0
+endfunction
+
+function s:GetLuauIndentIntern() abort
+  " find a non-blank line above the current line
+  let prevlnum = prevnonblank(v:lnum - 1)
+
+  " hit the start of the file, use zero indent
+  if prevlnum == 0
+    return 0
+  endif
+
+  " add a 'shiftwidth' after lines that start a block
+  let ind = indent(prevlnum)
+  let prevline = getline(prevlnum)
+  let attr = '@\%(\h\w*\|\[[^]]*\]\)\s*'
+  let stmt = 'if\>\|for\>\|while\>\|repeat\>\|else\>\|elseif\>\|do\>\|then\>'
+  let class = 'class\>\s\+\h\|declare\s\+class\>\s\+\h'
+  let func = 
'\%(\%(public\s\+\)\=function\|local\s\+function\|const\s\+function\|type\s\+function\|return\s\+function\)'
+  let declare_func = '^\s*\%(' .. attr .. '\)*declare\s\+function\>'
+  let midx = -1
+  if prevline =~# 
'^\s*\%(@\|\%(if\|for\|while\|repeat\|else\|elseif\|do\|then\|class\)\>\|declare\s\+class\>\)'
+    let midx = match(prevline, '^\s*\%(' .. attr .. '\)*\%(' .. stmt .. '\|' 
.. class .. '\)')
+  endif
+  if midx == -1
+    if prevline =~# '^\s*\%(' .. attr .. '\)*declare\s\+extern\s\+type\>'
+      let midx = match(prevline, '\<with\>\s*\%(--.*\)\=$')
+    endif
+    if midx == -1
+      let midx = match(prevline, '\%({\|(\|\[\)\s*\%(--\%([^[].*\)\?\)\?$')
+      if midx == -1 && stridx(prevline, 'function') != -1 && prevline !~# 
declare_func
+        let midx = match(prevline, '\<' .. func .. 
'\>\s*\%(\k\|[.:]\)\{-}\s*\%(<[^>]*>\s*\)\=(')
+      endif
+    endif
+  endif
+
+  if midx == -1 && prevline =~ '^\s*)\s*\%(:.\+\)\=\s*\%(--.*\)\=$'
+    let save_cursor = getcurpos()
+    call cursor(prevlnum, match(prevline, ')') + 1)
+    let [par_lnum, par_col] = searchpairpos('(', '', ')', 'bnW')
+    call setpos('.', save_cursor)
+    if par_lnum > 0
+      let funline = getline(par_lnum)
+      let funidx = match(funline, '\<' .. func .. '\>')
+      if funidx != -1 && funidx < par_col && funline !~# declare_func
+        let midx = match(prevline, ')')
+      endif
+    endif
+  endif
+
+  if midx != -1
+    " add 'shiftwidth' if this is not in a comment or string and the block
+    " does not close on the same line
+    if s:IsCode(prevlnum, midx + 1) && !s:HasBlockCloser(prevlnum)
+          \ && !(prevline =~# '^\s*\%(public\s\+\)\=function\>' && 
s:InDeclareClass(prevlnum))
+      let ind = ind + shiftwidth()
+    endif
+  endif
+
+  " subtract a 'shiftwidth' on end, else, elseif, until, '}', ')' and ']'
+  let midx = match(getline(v:lnum), 
'^\s*\%(end\>\|else\>\|elseif\>\|until\>\|}\|)\|\]\)')
+  if midx != -1
+    if s:IsCode(v:lnum, midx + 1)
+      let ind = ind - shiftwidth()
+    endif
+  endif
+
+  return ind
+endfunction
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim: nowrap sw=2 sts=2 ts=8 noet:
diff --git a/runtime/indent/testdir/luau.in b/runtime/indent/testdir/luau.in
new file mode 100644
index 000000000..0d3a4afaa
--- /dev/null
+++ b/runtime/indent/testdir/luau.in
@@ -0,0 +1,101 @@
+-- vim: set ft=luau sw=2 noet:
+
+-- START_INDENT
+@native
+function compute<T>(values: {T}): number
+local total: number = 0
+for _, value: T in values do
+if value then
+continue
+else
+total += 1
+end
+end
+return total
+end
+
+local function typed(
+name: string,
+count: number
+): boolean
+return count > 0
+end
+
+function singletonReturn(): "end"
+print("string singleton return")
+end
+
+if ready then -- end in a comment
+print("comment closer ignored")
+end
+
+local oneline = function() end
+local afterOneline = oneline
+
+declare function PluginManager(): PluginManager
+local afterDeclareFunction = true
+
+declare function WithArgs(
+arg: string
+): boolean
+local afterDeclareFunctionArgs = true
+
+local text = `function(`
+local afterText = text
+
+-- function(
+local afterLineComment = text
+
+class = makeClass()
+local afterClassIdentifier = class
+
+export type Box<T> = {
+read value: T,
+write name: string,
+callback: (T) -> (),
+}
+
+const function makeBox<T>(value: T): Box<T>
+return {
+value = value,
+name = `box {tostring(value)}`,
+callback = function(item: T)
+print(item)
+end,
+}
+end
+
+declare extern type Vector with
+x: number
+y: number
+z: number
+end
+
+declare class EnumItem extends Enum
+@deprecated
+function IsA(self, enumName: string): boolean
+Name: string
+end
+
+type function Optionalize(t)
+return types.optional(t)
+end
+
+local choice = if 2 == 3 then 4 else 5
+local raw = [=[
+if true then
+]=]
+local afterRaw = choice
+
+--[=[
+function ignored()
+]=]
+local afterComment = raw
+
+class Widget
+public Name: string
+public function Describe(self): string
+return `Widget {self.Name}`
+end
+end
+-- END_INDENT
diff --git a/runtime/indent/testdir/luau.ok b/runtime/indent/testdir/luau.ok
new file mode 100644
index 000000000..d38b79525
--- /dev/null
+++ b/runtime/indent/testdir/luau.ok
@@ -0,0 +1,101 @@
+-- vim: set ft=luau sw=2 noet:
+
+-- START_INDENT
+@native
+function compute<T>(values: {T}): number
+  local total: number = 0
+  for _, value: T in values do
+    if value then
+      continue
+    else
+      total += 1
+    end
+  end
+  return total
+end
+
+local function typed(
+  name: string,
+  count: number
+): boolean
+  return count > 0
+end
+
+function singletonReturn(): "end"
+  print("string singleton return")
+end
+
+if ready then -- end in a comment
+  print("comment closer ignored")
+end
+
+local oneline = function() end
+local afterOneline = oneline
+
+declare function PluginManager(): PluginManager
+local afterDeclareFunction = true
+
+declare function WithArgs(
+  arg: string
+): boolean
+local afterDeclareFunctionArgs = true
+
+local text = `function(`
+local afterText = text
+
+-- function(
+local afterLineComment = text
+
+class = makeClass()
+local afterClassIdentifier = class
+
+export type Box<T> = {
+  read value: T,
+  write name: string,
+  callback: (T) -> (),
+}
+
+const function makeBox<T>(value: T): Box<T>
+  return {
+    value = value,
+    name = `box {tostring(value)}`,
+    callback = function(item: T)
+      print(item)
+    end,
+  }
+end
+
+declare extern type Vector with
+  x: number
+  y: number
+  z: number
+end
+
+declare class EnumItem extends Enum
+  @deprecated
+  function IsA(self, enumName: string): boolean
+  Name: string
+end
+
+type function Optionalize(t)
+  return types.optional(t)
+end
+
+local choice = if 2 == 3 then 4 else 5
+local raw = [=[
+if true then
+]=]
+local afterRaw = choice
+
+--[=[
+function ignored()
+]=]
+local afterComment = raw
+
+class Widget
+  public Name: string
+  public function Describe(self): string
+    return `Widget {self.Name}`
+  end
+end
+-- END_INDENT
diff --git a/runtime/syntax/luau.vim b/runtime/syntax/luau.vim
index 59eccac10..e5b3490ee 100644
--- a/runtime/syntax/luau.vim
+++ b/runtime/syntax/luau.vim
@@ -1,15 +1,193 @@
 " Vim syntax file
-" Language:    Luau
-" Maintainer:  None yet
-" Last Change: 2023 Apr 30
+" Language:     Luau
+" Maintainer:   Lopy (@lopi-py)
+" Last Change:  2026 Jun 17
 
+" quit when a syntax file was already loaded
 if exists("b:current_syntax")
   finish
 endif
 
-" Luau is a superset of lua
-runtime! syntax/lua.vim
+let s:cpo_save = &cpo
+set cpo&vim
+
+syn case match
+syn sync minlines=300
+
+" comments
+syn keyword luauTodo contained TODO FIXME
+syn match   luauDirective contained 
"--!\%(strict\|nonstrict\|nocheck\|nolint\%(Global\)\=\|native\|optimize\)\>.*"
+
+" strings
+syn match  luauSpecial contained 
#\[[:digit:]]\{1,3}\|\x[[:xdigit:]]\{2}\|\u{[[:xdigit:]]\+}\|\z\s*\|\
\|\[^xu[:digit:]
]#
+syn region luauString2 matchgroup=luauStringDelimiter start="\[\z(=*\)\[" 
end="\]\z1\]" contains=@Spell
+syn region luauString  matchgroup=luauStringDelimiter start=+'+ end=+'+ 
skip=+\\\|\'+ contains=luauSpecial,@Spell
+syn region luauString  matchgroup=luauStringDelimiter start=+"+ end=+"+ 
skip=+\\\|\"+ contains=luauSpecial,@Spell
+syn region luauInterpString matchgroup=luauStringDelimiter start=+`+ end=+`+ 
skip=+\`+ contains=luauSpecial,luauInterp,@Spell
+syn region luauInterp contained transparent matchgroup=luauInterpDelimiter 
start=+\\@<!{+ end=+}+ contains=TOP,luauInterpBlock
+syn region luauInterpBlock contained transparent start=+{+ end=+}+ 
contains=TOP,luauInterpBlock
+
+" numbers
+syn match luauNumber "\<0_*[xX][[:xdigit:]_]*[[:xdigit:]][[:xdigit:]_]*\>"
+syn match luauNumber "\<0_*[bB][01_]*[01][01_]*\>"
+syn match luauNumber 
"\<\d[[:digit:]_]*\%(\.[[:digit:]_]*\)\=\%([eE][-+]\=[[:digit:]_]*\d[[:digit:]_]*\)\=\>"
+syn match luauNumber 
"\.\d[[:digit:]_]*\%([eE][-+]\=[[:digit:]_]*\d[[:digit:]_]*\)\=\>"
+
+" keywords
+syn keyword luauStatement return local break end
+syn keyword luauStatement do nextgroup=luauStatement skipwhite
+syn keyword luauStatement contained continue
+syn match   luauStatement 
"^\s*\zscontinue\>\ze\s*\%(;\|end\>\|else\>\|elseif\>\|until\>\|--.*\|$\)"
+syn match   luauStatement 
";\s*\zscontinue\>\ze\s*\%(;\|end\>\|else\>\|elseif\>\|until\>\|--.*\|$\)"
+syn keyword luauFunction function nextgroup=luauFunctionName,luauGenericParams 
skipwhite
+syn match   luauStatement 
"^\s*\%(@\%(\h\w*\|\[[^]]*\]\)\s*\)*\zsconst\>\ze\s\+\%(function\>\|\h\)"
+syn match   luauStatement 
"^\s*\%(@\%(\h\w*\|\[[^]]*\]\)\s*\)*\zsdeclare\>\ze\s\+\%(function\>\|class\>\s\+\h\|extern\s\+type\>\|\h\w*\s*:\)"
 nextgroup=luauStatement,luauDeclareName,luauFunction skipwhite
+syn match   luauStatement contained "\<class\>\ze\s\+\h" nextgroup=luauTypedef 
skipwhite
+syn match   luauDeclareName contained "\h\w*\ze\s*:" nextgroup=luauTypeColon 
skipwhite
+syn match   luauStatement "\<export\>\ze\s\+type\>"
+syn match   luauStatement contained "\<extern\>\ze\s\+type\>"
+syn match   luauStatement "\<extends\>\ze\s\+\h" nextgroup=luauTypeName 
skipwhite
+syn match   luauStatement "\<with\>\ze\s*\%(--.*\)\=$"
+syn match   luauModifier "\<public\>\ze\s\+\%(function\|\h\)"
+syn match   luauTypeKeyword "\<type\>\ze\s\+\h" 
nextgroup=luauTypeFunction,luauTypedef skipwhite
+syn keyword luauTypeFunction contained function nextgroup=luauFunctionName 
skipwhite
+syn match   luauFunctionName contained transparent "\h\w*\%([.:]\h\w*\)*" 
nextgroup=luauGenericParams skipwhite
+syn keyword luauCond if elseif
+syn keyword luauCond then else nextgroup=luauStatement skipwhite
+syn keyword luauRepeat while for until in
+syn keyword luauRepeat repeat nextgroup=luauStatement skipwhite
+syn keyword luauOperator and or not
+
+" operators and punctuation
+syn match luauSymbolOperator "[#+*/%^=<>~?-]\|\.\{3}\|\.\.=\="
+syn match luauSymbolOperator "->" 
nextgroup=luauSymbolOperator,luauConstant,luauTableType,luauTypePack,luauTypeName
 skipwhite
+syn match luauSymbolOperator "[|&]" 
nextgroup=luauConstant,luauTableType,luauTypePack,luauTypeName skipwhite
+syn match luauSymbolOperator "::"
+
+" tables
+syn region luauTableBlock transparent matchgroup=luauTable start="{" end="}" 
contains=TOP,luauStatement
+
+syn match   luauComment "--.*$" contains=luauTodo,luauDirective,@Spell
+syn region  luauComment matchgroup=luauCommentDelimiter start="--\[\z(=*\)\[" 
end="\]\z1\]" contains=luauTodo,@Spell
+
+" the first line may start with #!
+syn match luauComment "\%^#!.*"
+
+" attributes
+syn match   luauAttribute "@\h\w*"
+syn region  luauAttributeBlock matchgroup=luauAttributeDelimiter start="@\[" 
end="\]" 
contains=luauAttributeName,luauAttributeTable,luauString,luauString2,luauInterpString,luauNumber,luauConstant
+syn region  luauAttributeTable contained transparent matchgroup=luauTable 
start="{" end="}" 
contains=luauAttributeTable,luauString,luauString2,luauInterpString,luauNumber,luauConstant,luauSymbolOperator
+syn keyword luauAttributeName contained checked native deprecated
+
+" constants and types
+syn keyword luauConstant nil true false
+syn match   luauConstant "\.\.\."
+syn keyword luauSelf self
+syn match   luauSelf contained "(\s*\zsself\>\ze\s*:"
+syn cluster luauTypeCommon 
contains=luauGenericParams,luauString,luauString2,luauInterpString,luauNumber,luauConstant,luauSymbolOperator
+syn cluster luauTypeExpr 
contains=luauFunctionTypeParams,luauTableType,luauTypeParam,luauType,luauTypeKeyword,@luauTypeCommon
+syn region  luauGenericParams contained transparent 
matchgroup=luauSymbolOperator start="<" end=">" contains=@luauTypeExpr
+syn region  luauTypedefGenericParams contained transparent 
matchgroup=luauSymbolOperator start="<" end=">" contains=@luauTypeExpr 
nextgroup=luauTypeAliasAssign skipwhite
+syn region  luauExplicitTypeArgs transparent matchgroup=luauSymbolOperator 
start="<<" end=">>" contains=@luauTypeExpr
+syn match   luauTypeParam contained "\h\w*\%(\.\.\.\)\="
+syn match   luauTypedef "\h\w*" contained 
nextgroup=luauTypedefGenericParams,luauTypeAliasAssign skipwhite
+syn match   luauTypeAliasAssign contained "=" 
nextgroup=luauSymbolOperator,luauConstant,luauTableType,luauTypeName skipwhite
+syn region  luauTableType contained transparent matchgroup=luauTable start="{" 
end="}" 
contains=luauFunctionTypeParams,luauTableType,luauSelf,luauTypeIndexer,luauTableElementType,luauTypeColon,luauTypeKeyword,@luauTypeCommon
+syn match   luauTypeIndexer "^\s*\[\s*" nextgroup=luauType skipwhite
+syn match   luauTypeIndexer contained transparent "\[\s*" nextgroup=luauType 
skipwhite
+syn match   luauType contained "\h\w*\%(\.\h\w*\)*\ze\s*\]\s*:" 
nextgroup=luauGenericParams skipwhite
+syn match   luauTableElementType contained 
"\h\w*\%(\.\h\w*\)*\ze\s*[<?|&,;})]" nextgroup=luauGenericParams skipwhite
+syn match   luauTypeColon transparent ":" 
nextgroup=luauSymbolOperator,luauConstant,luauTableType,luauTypeAfterColon 
skipwhite
+syn match   luauTypeAfterColon contained 
"\h\w*\%(\.\h\w*\)*\%(\.\.\.\)\=\ze\s*\%([?=},)\]|&]\|,\|<\|->\|$\)" 
nextgroup=luauGenericParams skipwhite
+syn region  luauFunctionTypeParams transparent start="(\ze\%([^()]*\))\s*->" 
end=")\ze\s*->" 
contains=luauFunctionTypeParam,luauTypeColon,luauSelf,@luauTypeCommon
+syn match   luauFunctionTypeParam contained 
"\h\w*\%(\.\h\w*\)*\%(\.\.\.\)\=\ze\s*?\=\s*[,)|&]" nextgroup=luauGenericParams 
skipwhite
+syn match   luauFunctionReturnStart transparent ")\s*:\s*" 
nextgroup=luauSymbolOperator,luauFunctionTypeParams,luauConstant,luauTableType,luauTypePack,luauTypeName
 skipwhite
+syn region  luauTypePack contained transparent start="(\%([^()]*)\s*->\)\@!" 
end=")" 
contains=luauFunctionTypeParams,luauTableType,luauTypeName,@luauTypeCommon
+syn match   luauTypeName contained "\h\w*\%(\.\h\w*\)*\%(\.\.\.\)\=" 
nextgroup=luauGenericParams skipwhite
+syn match   luauTypeKeyword "\<\%(read\|write\)\>\ze\s*\%(\[\|\h\+\s*:\)" 
nextgroup=luauTypeIndexer skipwhite
+
+" metamethods
+syn keyword luauMetaMethod __index __newindex __mode __namecall __call __iter 
__len
+syn keyword luauMetaMethod __eq __add __sub __mul __div __idiv __mod __pow 
__unm
+syn keyword luauMetaMethod __lt __le __concat __type __metatable __tostring
+
+" methods
+syn match luauMethodColon transparent 
":\ze\s*\h\w*\s*\%(<<.\{-}>>\s*\|<[^>]*>\s*\)\=\%((\|{\|'\|\"\|\[\)" 
nextgroup=luauFunc skipwhite
+syn match luauFunc contained 
"\h\w*\ze\s*\%(<<.\{-}>>\s*\|<[^>]*>\s*\)\=\%((\|{\|'\|\"\|\[\)"
+
+" global functions and values
+syn keyword luauFunc assert error gcinfo getfenv getmetatable
+syn keyword luauFunc ipairs loadstring newproxy next pairs pcall print
+syn keyword luauFunc rawequal rawget rawlen rawset require select setfenv
+syn keyword luauFunc setmetatable tonumber tostring unpack xpcall
+syn keyword luauGlobal _G
+syn keyword luauBuiltinConstant _VERSION
+syn match   luauFunc "\<type\>\ze\s*\%(<<.\{-}>>\s*\)\=("
+syn match   luauFunc "\<typeof\>\ze\s*\%(<<.\{-}>>\s*\)\=("
+
+" standard library members
+syn match luauFunc 
/\<bit32\.\%(arshift\|band\|bnot\|bor\|btest\|bxor\|byteswap\|countlz\|countrz\|extract\|lrotate\|lshift\|replace\|rrotate\|rshift\)\>/
+syn match luauFunc 
/\<buffer\.\%(copy\|create\|fill\|fromstring\|len\|readbits\|readf32\|readf64\|readi16\|readi32\|readi8\|readstring\|readu16\|readu32\|readu8\)\>/
+syn match luauFunc 
/\<buffer\.\%(tostring\|writebits\|writef32\|writef64\|writei16\|writei32\|writei8\|writestring\|writeu16\|writeu32\|writeu8\)\>/
+syn match luauFunc 
/\<coroutine\.\%(close\|create\|isyieldable\|resume\|running\|status\|wrap\|yield\)\>/
+syn match luauFunc /\<debug\.\%(info\|traceback\)\>/
+syn match luauFunc 
/\<math\.\%(abs\|acos\|asin\|atan2\=\|ceil\|clamp\|cosh\=\|deg\|exp\|floor\|fmod\|frexp\)\>/
+syn match luauFunc 
/\<math\.\%(isfinite\|isinf\|isnan\|ldexp\|lerp\|log\|log10\|map\|max\|min\|modf\|noise\)\>/
+syn match luauFunc 
/\<math\.\%(pow\|rad\|random\%(seed\)\=\|round\|sign\|sinh\=\|sqrt\|tanh\=\)\>/
+syn match luauBuiltinConstant /\<math\.\%(huge\|pi\)\>/
+syn match luauFunc /\<os\.\%(clock\|date\|difftime\|time\)\>/
+syn match luauFunc 
/\<string\.\%(byte\|char\|find\|format\|gmatch\|gsub\|len\|lower\|match\|pack\%(size\)\=\|rep\|reverse\|split\|sub\|unpack\|upper\)\>/
+syn match luauFunc 
/\<table\.\%(clear\|clone\|concat\|create\|find\|foreachi\=\|freeze\|getn\|insert\|isfrozen\|maxn\|move\|pack\|remove\|sort\|unpack\)\>/
+syn match luauFunc /\<utf8\.\%(char\|codepoint\|codes\|len\|offset\)\>/
+syn match luauBuiltinConstant /\<utf8\.charpattern\>/
+syn match luauFunc 
/\<vector\.\%(abs\|angle\|ceil\|clamp\|create\|cross\|dot\|floor\|lerp\|magnitude\|max\|min\|normalize\|sign\)\>/
+syn match luauBuiltinConstant /\<vector\.\%(one\|zero\)\>/
+syn match luauFunc 
/\<types\.\%(any\|boolean\|buffer\|copy\|generic\|intersectionof\|never\|negationof\|newfunction\|newtable\|number\|optional\|singleton\|string\|thread\|unionof\|unknown\)\>/
+
+" define the default highlighting
+hi def link luauStatement          Statement
+hi def link luauRepeat             Repeat
+hi def link luauString             String
+hi def link luauString2            String
+hi def link luauInterpString       String
+hi def link luauStringDelimiter    luauString
+hi def link luauInterpDelimiter    Special
+hi def link luauNumber             Number
+hi def link luauOperator           Operator
+hi def link luauSymbolOperator     luauOperator
+hi def link luauConstant           Constant
+hi def link luauCond               Conditional
+hi def link luauFunction           Function
+hi def link luauMetaMethod         Function
+hi def link luauTable              Structure
+hi def link luauComment            Comment
+hi def link luauCommentDelimiter   luauComment
+hi def link luauDirective          PreProc
+hi def link luauTodo               Todo
+hi def link luauSpecial            SpecialChar
+hi def link luauFunc               Identifier
+hi def link luauGlobal             Identifier
+hi def link luauBuiltinConstant    Constant
+hi def link luauSelf               Identifier
+hi def link luauModifier           StorageClass
+hi def link luauType               Type
+hi def link luauTypeAfterColon     luauType
+hi def link luauTableElementType   luauType
+hi def link luauTypeAliasAssign    luauSymbolOperator
+hi def link luauFunctionTypeParam  luauType
+hi def link luauTypeName           luauType
+hi def link luauTypedef            Typedef
+hi def link luauTypeParam          Type
+hi def link luauTypeKeyword        Keyword
+hi def link luauTypeFunction       luauFunction
+hi def link luauDeclareName        Identifier
+hi def link luauAttribute          PreProc
+hi def link luauAttributeName      PreProc
+hi def link luauAttributeDelimiter PreProc
 
 let b:current_syntax = "luau"
 
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
 " vim: nowrap sw=2 sts=2 ts=8 noet:

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1waIfi-004Rga-I3%40256bit.org.

Raspunde prin e-mail lui