aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/_editor.lua1
-rw-r--r--runtime/lua/vim/filetype.lua380
-rw-r--r--runtime/lua/vim/filetype/detect.lua843
-rw-r--r--runtime/lua/vim/lsp.lua87
-rw-r--r--runtime/lua/vim/lsp/buf.lua83
-rw-r--r--runtime/lua/vim/lsp/handlers.lua8
-rw-r--r--runtime/lua/vim/lsp/health.lua3
-rw-r--r--runtime/lua/vim/lsp/rpc.lua4
-rw-r--r--runtime/lua/vim/lsp/util.lua35
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua8
10 files changed, 1047 insertions, 405 deletions
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index dc25d68f61..e6ab48f30d 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -406,7 +406,6 @@ function vim.defer_fn(fn, timeout)
timeout,
0,
vim.schedule_wrap(function()
- timer:stop()
timer:close()
fn()
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index fed0231ae9..c26a43f776 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -283,6 +283,7 @@ local extension = {
hjson = 'hjson',
hog = 'hog',
hws = 'hollywood',
+ hoon = 'hoon',
htt = 'httest',
htb = 'httest',
iba = 'ibasic',
@@ -418,6 +419,7 @@ local extension = {
tsc = 'monk',
isc = 'monk',
moo = 'moo',
+ moon = 'moonscript',
mp = 'mp',
mof = 'msidl',
odl = 'msidl',
@@ -786,8 +788,8 @@ local extension = {
zut = 'zimbutempl',
zsh = 'zsh',
vala = 'vala',
- E = function()
- vim.fn['dist#ft#FTe']()
+ E = function(path, bufnr)
+ return require('vim.filetype.detect').e(bufnr)
end,
EU = function(path, bufnr)
return require('vim.filetype.detect').euphoria(bufnr)
@@ -804,68 +806,68 @@ local extension = {
EXW = function(path, bufnr)
return require('vim.filetype.detect').euphoria(bufnr)
end,
- PL = function()
- vim.fn['dist#ft#FTpl']()
+ PL = function(path, bufnr)
+ return require('vim.filetype.detect').pl(bufnr)
end,
R = function(path, bufnr)
- require('vim.filetype.detect').r(bufnr)
+ return require('vim.filetype.detect').r(bufnr)
end,
- asm = function()
- vim.fn['dist#ft#FTasm']()
+ asm = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
end,
- bas = function()
- vim.fn['dist#ft#FTbas']()
+ bas = function(path, bufnr)
+ return require('vim.filetype.detect').bas(bufnr)
end,
- bi = function()
- vim.fn['dist#ft#FTbas']()
+ bi = function(path, bufnr)
+ return require('vim.filetype.detect').bas(bufnr)
end,
- bm = function()
- vim.fn['dist#ft#FTbas']()
+ bm = function(path, bufnr)
+ return require('vim.filetype.detect').bas(bufnr)
end,
- bash = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ bash = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
btm = function(path, bufnr)
return require('vim.filetype.detect').btm(bufnr)
end,
- c = function()
- vim.fn['dist#ft#FTlpc']()
+ c = function(path, bufnr)
+ return require('vim.filetype.detect').lpc(bufnr)
end,
- ch = function()
- vim.fn['dist#ft#FTchange']()
+ ch = function(path, bufnr)
+ return require('vim.filetype.detect').change(bufnr)
end,
- com = function()
- vim.fn['dist#ft#BindzoneCheck']('dcl')
+ com = function(path, bufnr)
+ return require('vim.filetype.detect').bindzone(bufnr, 'dcl')
end,
- cpt = function()
- vim.fn['dist#ft#FThtml']()
+ cpt = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
end,
- csh = function()
- vim.fn['dist#ft#CSH']()
+ csh = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
- d = function()
- vim.fn['dist#ft#DtraceCheck']()
+ d = function(path, bufnr)
+ return require('vim.filetype.detect').dtrace(bufnr)
end,
- db = function()
- vim.fn['dist#ft#BindzoneCheck']('')
+ db = function(path, bufnr)
+ return require('vim.filetype.detect').bindzone(bufnr, '')
end,
- dtml = function()
- vim.fn['dist#ft#FThtml']()
+ dtml = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
end,
- e = function()
- vim.fn['dist#ft#FTe']()
+ e = function(path, bufnr)
+ return require('vim.filetype.detect').e(bufnr)
end,
- ebuild = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ ebuild = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- eclass = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ eclass = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
ent = function(path, bufnr)
return require('vim.filetype.detect').ent(bufnr)
end,
- env = function()
- vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1))
+ env = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
eu = function(path, bufnr)
return require('vim.filetype.detect').euphoria(bufnr)
@@ -883,55 +885,55 @@ local extension = {
return require('vim.filetype.detect').euphoria(bufnr)
end,
frm = function(path, bufnr)
- require('vim.filetype.detect').frm(bufnr)
+ return require('vim.filetype.detect').frm(bufnr)
end,
- fs = function()
- vim.fn['dist#ft#FTfs']()
+ fs = function(path, bufnr)
+ return require('vim.filetype.detect').fs(bufnr)
end,
h = function(path, bufnr)
- require('vim.filetype.detect').header(bufnr)
+ return require('vim.filetype.detect').header(bufnr)
end,
- htm = function()
- vim.fn['dist#ft#FThtml']()
+ htm = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
end,
- html = function()
- vim.fn['dist#ft#FThtml']()
+ html = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
end,
- i = function()
- vim.fn['dist#ft#FTprogress_asm']()
+ i = function(path, bufnr)
+ return require('vim.filetype.detect').progress_asm(bufnr)
end,
idl = function(path, bufnr)
- require('vim.filetype.detect').idl(bufnr)
+ return require('vim.filetype.detect').idl(bufnr)
end,
- inc = function()
- vim.fn['dist#ft#FTinc']()
+ inc = function(path, bufnr)
+ return require('vim.filetype.detect').inc(bufnr)
end,
inp = function(path, bufnr)
- require('vim.filetype.detect').inp(bufnr)
+ return require('vim.filetype.detect').inp(bufnr)
end,
- ksh = function()
- vim.fn['dist#ft#SetFileTypeSH']('ksh')
+ ksh = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'ksh')
end,
- lst = function()
- vim.fn['dist#ft#FTasm']()
+ lst = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
end,
- m = function()
- vim.fn['dist#ft#FTm']()
+ m = function(path, bufnr)
+ return require('vim.filetype.detect').m(bufnr)
end,
- mac = function()
- vim.fn['dist#ft#FTasm']()
+ mac = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
end,
mc = function(path, bufnr)
- require('vim.filetype.detect').mc(bufnr)
+ return require('vim.filetype.detect').mc(bufnr)
end,
- mm = function()
- vim.fn['dist#ft#FTmm']()
+ mm = function(path, bufnr)
+ return require('vim.filetype.detect').mm(bufnr)
end,
mms = function(path, bufnr)
- require('vim.filetype.detect').mms(bufnr)
+ return require('vim.filetype.detect').mms(bufnr)
end,
- p = function()
- vim.fn['dist#ft#FTprogress_pascal']()
+ p = function(path, bufnr)
+ return require('vim.filetype.detect').progress_pascal(bufnr)
end,
patch = function(path, bufnr)
local firstline = getline(bufnr, 1)
@@ -941,65 +943,65 @@ local extension = {
return 'diff'
end
end,
- pl = function()
- vim.fn['dist#ft#FTpl']()
+ pl = function(path, bufnr)
+ return require('vim.filetype.detect').pl(bufnr)
end,
- pp = function()
- vim.fn['dist#ft#FTpp']()
+ pp = function(path, bufnr)
+ return require('vim.filetype.detect').pp(bufnr)
end,
- pro = function()
- vim.fn['dist#ft#ProtoCheck']('idlang')
+ pro = function(path, bufnr)
+ return require('vim.filetype.detect').proto(bufnr, 'idlang')
end,
- pt = function()
- vim.fn['dist#ft#FThtml']()
+ pt = function(path, bufnr)
+ return require('vim.filetype.detect').html('idlang')
end,
r = function(path, bufnr)
- require('vim.filetype.detect').r(bufnr)
+ return require('vim.filetype.detect').r(bufnr)
end,
rdf = function(path, bufnr)
- require('vim.filetype.detect').redif(bufnr)
+ return require('vim.filetype.detect').redif(bufnr)
end,
- rules = function()
- vim.fn['dist#ft#FTRules']()
+ rules = function(path, bufnr)
+ return require('vim.filetype.detect').rules(path, bufnr)
end,
sc = function(path, bufnr)
- require('vim.filetype.detect').sc(bufnr)
+ return require('vim.filetype.detect').sc(bufnr)
end,
scd = function(path, bufnr)
- require('vim.filetype.detect').scd(bufnr)
+ return require('vim.filetype.detect').scd(bufnr)
end,
- sh = function()
- vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1))
+ sh = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
- shtml = function()
- vim.fn['dist#ft#FThtml']()
+ shtml = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
end,
sql = function(path, bufnr)
- require('vim.filetype.detect').sql(bufnr)
+ return require('vim.filetype.detect').sql(bufnr)
end,
- stm = function()
- vim.fn['dist#ft#FThtml']()
+ stm = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
end,
- tcsh = function()
- vim.fn['dist#ft#SetFileTypeShell']('tcsh')
+ tcsh = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
end,
- tex = function()
- vim.fn['dist#ft#FTtex']()
+ tex = function(path, bufnr)
+ return require('vim.filetype.detect').tex(path, bufnr)
end,
tf = function(path, bufnr)
- require('vim.filetype.detect').tf(bufnr)
+ return require('vim.filetype.detect').tf(bufnr)
end,
w = function(path, bufnr)
- require('vim.filetype.detect').progress_cweb(bufnr)
+ return require('vim.filetype.detect').progress_cweb(bufnr)
end,
xml = function(path, bufnr)
- require('vim.filetype.detect').xml(bufnr)
+ return require('vim.filetype.detect').xml(bufnr)
end,
y = function(path, bufnr)
- require('vim.filetype.detect').y(bufnr)
+ return require('vim.filetype.detect').y(bufnr)
end,
zsql = function(path, bufnr)
- require('vim.filetype.detect').sql(bufnr)
+ return require('vim.filetype.detect').sql(bufnr)
end,
txt = function(path, bufnr)
--helpfiles match *.txt, but should have a modeline as last line
@@ -1058,6 +1060,7 @@ local filename = {
['.dircolors'] = 'dircolors',
['/etc/dnsmasq.conf'] = 'dnsmasq',
Containerfile = 'dockerfile',
+ dockerfile = 'dockerfile',
Dockerfile = 'dockerfile',
npmrc = 'dosini',
['/etc/yum.conf'] = 'dosini',
@@ -1075,16 +1078,16 @@ local filename = {
exports = 'exports',
['.fetchmailrc'] = 'fetchmail',
fvSchemes = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
fvSolution = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
fvConstraints = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
fvModels = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
fstab = 'fstab',
mtab = 'fstab',
@@ -1300,63 +1303,63 @@ local filename = {
['.zcompdump'] = 'zsh',
['.zshenv'] = 'zsh',
['.zfbfmarks'] = 'zsh',
- ['.alias'] = function()
- vim.fn['dist#ft#CSH']()
+ ['.alias'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
- ['.bashrc'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ ['.bashrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- ['.cshrc'] = function()
- vim.fn['dist#ft#CSH']()
+ ['.cshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
- ['.env'] = function()
- vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1))
+ ['.env'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
- ['.kshrc'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('ksh')
+ ['.kshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'ksh')
end,
- ['.login'] = function()
- vim.fn['dist#ft#CSH']()
+ ['.login'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
- ['.profile'] = function()
- vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1))
+ ['.profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
- ['.tcshrc'] = function()
- vim.fn['dist#ft#SetFileTypeShell']('tcsh')
+ ['.tcshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
end,
- ['/etc/profile'] = function()
- vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1))
+ ['/etc/profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
- APKBUILD = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ APKBUILD = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- PKGBUILD = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ PKGBUILD = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- ['bash.bashrc'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ ['bash.bashrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- bashrc = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ bashrc = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
crontab = starsetf('crontab'),
- ['csh.cshrc'] = function()
- vim.fn['dist#ft#CSH']()
+ ['csh.cshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
- ['csh.login'] = function()
- vim.fn['dist#ft#CSH']()
+ ['csh.login'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
- ['csh.logout'] = function()
- vim.fn['dist#ft#CSH']()
+ ['csh.logout'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
- ['indent.pro'] = function()
- vim.fn['dist#ft#ProtoCheck']('indent')
+ ['indent.pro'] = function(path, bufnr)
+ return require('vim.filetype.detect').proto(bufnr, 'indent')
end,
- ['tcsh.login'] = function()
- vim.fn['dist#ft#SetFileTypeShell']('tcsh')
+ ['tcsh.login'] = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
end,
- ['tcsh.tcshrc'] = function()
- vim.fn['dist#ft#SetFileTypeShell']('tcsh')
+ ['tcsh.tcshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
end,
-- END FILENAME
}
@@ -1528,32 +1531,32 @@ local pattern = {
['.*/etc/xdg/menus/.*%.menu'] = 'xml',
['.*Xmodmap'] = 'xmodmap',
['.*/etc/zprofile'] = 'zsh',
- ['%.bash[_-]aliases'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ ['%.bash[_-]aliases'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- ['%.bash[_-]logout'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ ['%.bash[_-]logout'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- ['%.bash[_-]profile'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ ['%.bash[_-]profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
- ['%.cshrc.*'] = function()
- vim.fn['dist#ft#CSH']()
+ ['%.cshrc.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
['%.gtkrc.*'] = starsetf('gtkrc'),
- ['%.kshrc.*'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('ksh')
+ ['%.kshrc.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'ksh')
end,
- ['%.login.*'] = function()
- vim.fn['dist#ft#CSH']()
+ ['%.login.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
end,
['%.neomuttrc.*'] = starsetf('neomuttrc'),
- ['%.profile.*'] = function()
- vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1))
+ ['%.profile.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
['%.reminders.*'] = starsetf('remind'),
- ['%.tcshrc.*'] = function()
- vim.fn['dist#ft#SetFileTypeShell']('tcsh')
+ ['%.tcshrc.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
end,
['%.zcompdump.*'] = starsetf('zsh'),
['%.zlog.*'] = starsetf('zsh'),
@@ -1561,11 +1564,11 @@ local pattern = {
['.*%.[1-9]'] = function(path, bufnr)
return require('vim.filetype.detect').nroff(bufnr)
end,
- ['.*%.[aA]'] = function()
- vim.fn['dist#ft#FTasm']()
+ ['.*%.[aA]'] = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
end,
- ['.*%.[sS]'] = function()
- vim.fn['dist#ft#FTasm']()
+ ['.*%.[sS]'] = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
end,
['.*%.properties_.._.._.*'] = starsetf('jproperties'),
['.*%.vhdl_[0-9].*'] = starsetf('vhdl'),
@@ -1575,8 +1578,8 @@ local pattern = {
['.*/Xresources/.*'] = starsetf('xdefaults'),
['.*/app%-defaults/.*'] = starsetf('xdefaults'),
['.*/bind/db%..*'] = starsetf('bindzone'),
- ['.*/debian/patches/.*'] = function()
- vim.fn['dist#ft#Dep3patch']()
+ ['.*/debian/patches/.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').dep3patch(path, bufnr)
end,
['.*/etc/Muttrc%.d/.*'] = starsetf('muttrc'),
['.*/etc/apache2/.*%.conf.*'] = starsetf('apache'),
@@ -1592,8 +1595,8 @@ local pattern = {
['.*/etc/logcheck/.*%.d.*/.*'] = starsetf('logcheck'),
['.*/etc/modprobe%..*'] = starsetf('modconf'),
['.*/etc/pam%.d/.*'] = starsetf('pamconf'),
- ['.*/etc/profile'] = function()
- vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1))
+ ['.*/etc/profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
['.*/etc/proftpd/.*%.conf.*'] = starsetf('apachestyle'),
['.*/etc/proftpd/conf%..*/.*'] = starsetf('apachestyle'),
@@ -1621,8 +1624,8 @@ local pattern = {
['access%.conf.*'] = starsetf('apache'),
['apache%.conf.*'] = starsetf('apache'),
['apache2%.conf.*'] = starsetf('apache'),
- ['bash%-fc[-%.]'] = function()
- vim.fn['dist#ft#SetFileTypeSH']('bash')
+ ['bash%-fc[-%.]'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
end,
['cabal%.project%..*'] = starsetf('cabalproject'),
['crontab%..*'] = starsetf('crontab'),
@@ -1650,28 +1653,28 @@ local pattern = {
['neomutt' .. string.rep('[%w_-]', 6)] = 'mail',
['/tmp/SLRN[0-9A-Z.]+'] = 'mail',
['[a-zA-Z0-9].*Dict'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['[a-zA-Z0-9].*Dict%..*'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['[a-zA-Z].*Properties'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['[a-zA-Z].*Properties%..*'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['.*Transport%..*'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['.*/constant/g'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['.*/0/.*'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['.*/0%.orig/.*'] = function(path, bufnr)
- require('vim.filetype.detect').foam(bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
end,
['.*/etc/sensors%.d/[^.].*'] = starsetf('sensors'),
['.*%.git/.*'] = function(path, bufnr)
@@ -1680,24 +1683,29 @@ local pattern = {
return 'git'
end
end,
- ['.*%.[Cc][Ff][Gg]'] = function()
- vim.fn['dist#ft#FTcfg']()
- end,
- ['.*%.[Dd][Aa][Tt]'] = function()
- vim.fn['dist#ft#FTdat']()
+ ['.*%.[Cc][Ff][Gg]'] = {
+ function(path, bufnr)
+ return require('vim.filetype.detect').cfg(bufnr)
+ end,
+ -- Decrease the priority to avoid conflicts with more specific patterns
+ -- such as '.*/etc/a2ps/.*%.cfg', '.*enlightenment/.*%.cfg', etc.
+ { priority = -1 },
+ },
+ ['.*%.[Dd][Aa][Tt]'] = function(path, bufnr)
+ return require('vim.filetype.detect').dat(bufnr)
end,
- ['.*%.[Mm][Oo][Dd]'] = function()
- vim.fn['dist#ft#FTmod']()
+ ['.*%.[Mm][Oo][Dd]'] = function(path, bufnr)
+ return require('vim.filetype.detect').mod(path, bufnr)
end,
- ['.*%.[Ss][Rr][Cc]'] = function()
- vim.fn['dist#ft#FTsrc']()
+ ['.*%.[Ss][Rr][Cc]'] = function(path, bufnr)
+ return require('vim.filetype.detect').src(bufnr)
end,
['.*%.[Ss][Uu][Bb]'] = 'krl',
- ['.*%.[Pp][Rr][Gg]'] = function()
- vim.fn['dist#ft#FTprg']()
+ ['.*%.[Pp][Rr][Gg]'] = function(path, bufnr)
+ return require('vim.filetype.detect').prg(bufnr)
end,
- ['.*%.[Ss][Yy][Ss]'] = function()
- vim.fn['dist#ft#FTsys']()
+ ['.*%.[Ss][Yy][Ss]'] = function(path, bufnr)
+ return require('vim.filetype.detect').sys(bufnr)
end,
-- Neovim only
['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries
diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index 4c363e7403..f195693dcf 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -1,19 +1,35 @@
+-- Contains filetype detection functions converted to Lua from Vim's autoload/runtime/dist/ft.vim file.
+
+-- Here are a few guidelines to follow when porting a new function:
+-- * Sort the function alphabetically and omit 'ft' or 'check' from the new function name.
+-- * Use ':find' instead of ':match' / ':sub' if possible.
+-- * When '=~' is used to match a pattern, there are two possibilities:
+-- - If the pattern only contains lowercase characters, treat the comparison as case-insensitive.
+-- - Otherwise, treat it as case-sensitive.
+-- (Basically, we apply 'smartcase': if upper case characters are used in the original pattern, then
+-- it's likely that case does matter).
+-- * When '\k', '\<' or '\>' is used in a pattern, use the 'matchregex' function.
+-- Note that vim.regex is case-sensitive by default, so add the '\c' flag if only lowercase letters
+-- are present in the pattern:
+-- Example:
+-- `if line =~ '^\s*unwind_protect\>'` => `if matchregex(line, [[\c^\s*unwind_protect\>]])`
+
local M = {}
---@private
-local function getlines(bufnr, start_lnum, end_lnum, opts)
+local function getlines(bufnr, start_lnum, end_lnum)
if not end_lnum then
-- Return a single line as a string
return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1]
end
-
- local lines = vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
- opts = opts or {}
- return opts.concat and (table.concat(lines) or '') or lines
+ return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
end
---@private
local function findany(s, patterns)
+ if s == nil then
+ return false
+ end
for _, v in ipairs(patterns) do
if s:find(v) then
return true
@@ -22,22 +38,117 @@ local function findany(s, patterns)
return false
end
+---@private
+local function nextnonblank(bufnr, start_lnum)
+ for _, line in ipairs(getlines(bufnr, start_lnum, -1)) do
+ if not line:find('^%s*$') then
+ return line
+ end
+ end
+ return nil
+end
+
+---@private
+local matchregex = (function()
+ local cache = {}
+ return function(line, pattern)
+ if line == nil then
+ return nil
+ end
+ if not cache[pattern] then
+ cache[pattern] = vim.regex(pattern)
+ end
+ return cache[pattern]:match_str(line)
+ end
+end)()
+
+---@private
+local did_filetype = function()
+ return vim.fn.did_filetype() ~= 0
+end
+
-- luacheck: push no unused args
-- luacheck: push ignore 122
-function M.asm(path, bufnr) end
+-- This function checks for the kind of assembly that is wanted by the user, or
+-- can be detected from the first five lines of the file.
+function M.asm(bufnr)
+ -- Make sure b:asmsyntax exists
+ if not vim.b[bufnr].asmsyntax then
+ vim.b[bufnr].asmsyntax = ''
+ end
+
+ if vim.b[bufnr].asmsyntax == '' then
+ M.asm_syntax(bufnr)
+ end
+
+ -- If b:asmsyntax still isn't set, default to asmsyntax or GNU
+ if vim.b[bufnr].asmsyntax == '' then
+ if vim.g.asmsyntax and vim.g.asmsyntax ~= 0 then
+ vim.b[bufnr].asmsyntax = vim.g.asmsyntax
+ else
+ vim.b[bufnr].asmsyntax = 'asm'
+ end
+ end
+ return vim.fn.fnameescape(vim.b[bufnr].asmsyntax)
+end
+
+-- Checks the first 5 lines for a asmsyntax=foo override.
+-- Only whitespace characters can be present immediately before or after this statement.
+function M.asm_syntax(bufnr)
+ local lines = table.concat(getlines(bufnr, 1, 5), ' '):lower()
+ local match = lines:match('%sasmsyntax=([a-zA-Z0-9]+)%s')
+ if match then
+ vim.b['asmsyntax'] = match
+ elseif findany(lines, { '%.title', '%.ident', '%.macro', '%.subtitle', '%.library' }) then
+ vim.b['asmsyntax'] = 'vmasm'
+ end
+end
-function M.asm_syntax(path, bufnr) end
+local visual_basic_content = { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform', 'begin vb%.usercontrol' }
+
+-- See frm() for Visual Basic form file detection
+function M.bas(bufnr)
+ if vim.g.filetype_bas then
+ return vim.g.filetype_bas
+ end
-function M.bas(path, bufnr) end
+ -- Most frequent FreeBASIC-specific keywords in distro files
+ local fb_keywords =
+ [[\c^\s*\%(extern\|var\|enum\|private\|scope\|union\|byref\|operator\|constructor\|delete\|namespace\|public\|property\|with\|destructor\|using\)\>\%(\s*[:=(]\)\@!]]
+ local fb_preproc =
+ [[\c^\s*\%(#\a\+\|option\s\+\%(byval\|dynamic\|escape\|\%(no\)\=gosub\|nokeyword\|private\|static\)\>\)]]
-function M.bindzone(path, bufnr) end
+ local fb_comment = "^%s*/'"
+ -- OPTION EXPLICIT, without the leading underscore, is common to many dialects
+ local qb64_preproc = [[\c^\s*\%($\a\+\|option\s\+\%(_explicit\|_\=explicitarray\)\>\)]]
+
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if line:find(fb_comment) or matchregex(line, fb_preproc) or matchregex(line, fb_keywords) then
+ return 'freebasic'
+ elseif matchregex(line, qb64_preproc) then
+ return 'qb64'
+ elseif findany(line:lower(), visual_basic_content) then
+ return 'vb'
+ end
+ end
+ return 'basic'
+end
+
+function M.bindzone(bufnr, default)
+ local lines = table.concat(getlines(bufnr, 1, 4))
+ if findany(lines, { '^; <<>> DiG [0-9%.]+.* <<>>', '%$ORIGIN', '%$TTL', 'IN%s+SOA' }) then
+ return 'bindzone'
+ else
+ return default
+ end
+end
function M.btm(bufnr)
if vim.g.dosbatch_syntax_for_btm and vim.g.dosbatch_syntax_for_btm ~= 0 then
- vim.bo[bufnr].filetype = 'dosbatch'
+ return 'dosbatch'
else
- vim.bo[bufnr].filetype = 'btm'
+ return 'btm'
end
end
@@ -47,38 +158,131 @@ local function is_rapid(bufnr, extension)
local line = getlines(bufnr, 1):lower()
return findany(line, { 'eio:cfg', 'mmc:cfg', 'moc:cfg', 'proc:cfg', 'sio:cfg', 'sys:cfg' })
end
- local first = '^%s*module%s+%S+%s*'
- -- Called from mod, prg or sys functions
- for _, line in ipairs(getlines(bufnr, 1, -1)) do
- if not line:find('^%s*$') then
- return findany(line:lower(), { '^%s*%%%%%%', first .. '(', first .. '$' })
- end
+ local line = nextnonblank(bufnr, 1)
+ if line then
+ -- Called from mod, prg or sys functions
+ return matchregex(line:lower(), [[\c\v^\s*%(\%{3}|module\s+\k+\s*%(\(|$))]])
end
- -- Only found blank lines
return false
end
function M.cfg(bufnr)
if vim.g.filetype_cfg then
- vim.bo[bufnr].filetype = vim.g.filetype_cfg
+ return vim.g.filetype_cfg
elseif is_rapid(bufnr, 'cfg') then
- vim.bo[bufnr].filetype = 'rapid'
+ return 'rapid'
else
- vim.bo[bufnr].filetype = 'cfg'
+ return 'cfg'
end
end
-function M.change(path, bufnr) end
+-- This function checks if one of the first ten lines start with a '@'. In
+-- that case it is probably a change file.
+-- If the first line starts with # or ! it's probably a ch file.
+-- If a line has "main", "include", "//" or "/*" it's probably ch.
+-- Otherwise CHILL is assumed.
+function M.change(bufnr)
+ local first_line = getlines(bufnr, 1)
+ if findany(first_line, { '^#', '^!' }) then
+ return 'ch'
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if line:find('^@') then
+ return 'change'
+ end
+ if line:find('MODULE') then
+ return 'chill'
+ elseif findany(line:lower(), { 'main%s*%(', '#%s*include', '//' }) then
+ return 'ch'
+ end
+ end
+ return 'chill'
+end
+
+function M.csh(path, bufnr)
+ if did_filetype() then
+ -- Filetype was already detected
+ return
+ end
+ if vim.g.filetype_csh then
+ return M.shell(path, bufnr, vim.g.filetype_csh)
+ elseif string.find(vim.o.shell, 'tcsh') then
+ return M.shell(path, bufnr, 'tcsh')
+ else
+ return M.shell(path, bufnr, 'csh')
+ end
+end
-function M.csh(path, bufnr) end
+-- Determine if a *.dat file is Kuka Robot Language
+function M.dat(bufnr)
+ if vim.g.filetype_dat then
+ return vim.g.filetype_dat
+ end
+ local line = nextnonblank(bufnr, 1)
+ if matchregex(line, [[\c\v^\s*%(\&\w+|defdat>)]]) then
+ return 'krl'
+ end
+end
-function M.dat(path, bufnr) end
+-- This function is called for all files under */debian/patches/*, make sure not
+-- to non-dep3patch files, such as README and other text files.
+function M.dep3patch(path, bufnr)
+ local file_name = vim.fn.fnamemodify(path, ':t')
+ if file_name == 'series' then
+ return
+ end
-function M.dep3patch(path, bufnr) end
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if
+ findany(line, {
+ '^Description:',
+ '^Subject:',
+ '^Origin:',
+ '^Bug:',
+ '^Forwarded:',
+ '^Author:',
+ '^From:',
+ '^Reviewed%-by:',
+ '^Acked%-by:',
+ '^Last%-Updated:',
+ '^Applied%-Upstream:',
+ })
+ then
+ return 'dep3patch'
+ elseif line:find('^%-%-%-') then
+ -- End of headers found. stop processing
+ return
+ end
+ end
+end
-function M.dtrace(path, bufnr) end
+function M.dtrace(bufnr)
+ if did_filetype() then
+ -- Filetype was already detected
+ return
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if matchregex(line, [[\c^module\>\|^import\>]]) then
+ -- D files often start with a module and/or import statement.
+ return 'd'
+ elseif findany(line, { '^#!%S+dtrace', '#pragma%s+D%s+option', ':%S-:%S-:' }) then
+ return 'dtrace'
+ end
+ end
+ return 'd'
+end
-function M.e(path, bufnr) end
+function M.e(bufnr)
+ if vim.g.filetype_euphoria then
+ return vim.g.filetype_euphoria
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if findany(line, { "^%s*<'%s*$", "^%s*'>%s*$" }) then
+ return 'specman'
+ end
+ end
+ return 'eiffel'
+end
-- This function checks for valid cl syntax in the first five lines.
-- Look for either an opening comment, '#', or a block start, '{'.
@@ -86,37 +290,34 @@ function M.e(path, bufnr) end
function M.ent(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find('^%s*[#{]') then
- vim.bo[bufnr].filetype = 'cl'
- return
+ return 'cl'
elseif not line:find('^%s*$') then
-- Not a blank line, not a comment, and not a block start,
-- so doesn't look like valid cl code.
break
end
end
- vim.bo[bufnr].filetype = 'dtd'
+ return 'dtd'
end
function M.euphoria(bufnr)
if vim.g.filetype_euphoria then
- vim.bo[bufnr].filetype = vim.g.filetype_euphoria
+ return vim.g.filetype_euphoria
else
- vim.bo[bufnr].filetype = 'euphoria3'
+ return 'euphoria3'
end
end
function M.ex(bufnr)
if vim.g.filetype_euphoria then
- vim.bo[bufnr].filetype = vim.g.filetype_euphoria
+ return vim.g.filetype_euphoria
else
for _, line in ipairs(getlines(bufnr, 1, 100)) do
- -- TODO: in the Vim regex, \> is used to match the end of the word, can this be omitted?
- if findany(line, { '^%-%-', '^ifdef', '^include' }) then
- vim.bo[bufnr].filetype = 'euphoria3'
- return
+ if matchregex(line, [[\c^--\|^ifdef\>\|^include\>]]) then
+ return 'euphoria3'
end
end
- vim.bo[bufnr].filetype = 'elixir'
+ return 'elixir'
end
end
@@ -129,80 +330,185 @@ function M.foam(bufnr)
if line:find('^FoamFile') then
foam_file = true
elseif foam_file and line:find('^%s*object') then
- vim.bo[bufnr].filetype = 'foam'
- return
+ return 'foam'
end
end
end
function M.frm(bufnr)
if vim.g.filetype_frm then
- vim.bo[bufnr].filetype = vim.g.filetype_frm
+ return vim.g.filetype_frm
+ end
+ local lines = table.concat(getlines(bufnr, 1, 5)):lower()
+ if findany(lines, visual_basic_content) then
+ return 'vb'
else
- -- Always ignore case
- local lines = getlines(bufnr, 1, 5, { concat = true }):lower()
- if findany(lines, { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform' }) then
- vim.bo[bufnr].filetype = 'vb'
- else
- vim.bo[bufnr].filetype = 'form'
- end
+ return 'form'
end
end
-function M.fs(path, bufnr) end
+-- Distinguish between Forth and F#.
+function M.fs(bufnr)
+ if vim.g.filetype_fs then
+ return vim.g.filetype_fs
+ end
+ local line = nextnonblank(bufnr, 1)
+ if findany(line, { '^%s*%.?%( ', '^%s*\\G? ', '^\\$', '^%s*: %S' }) then
+ return 'forth'
+ else
+ return 'fsharp'
+ end
+end
function M.header(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 200)) do
- if findany(line, { '^@interface', '^@end', '^@class' }) then
+ if findany(line:lower(), { '^@interface', '^@end', '^@class' }) then
if vim.g.c_syntax_for_h then
- vim.bo[bufnr].filetype = 'objc'
+ return 'objc'
else
- vim.bo[bufnr].filetype = 'objcpp'
+ return 'objcpp'
end
- return
end
end
if vim.g.c_syntax_for_h then
- vim.bo[bufnr].filetype = 'c'
+ return 'c'
elseif vim.g.ch_syntax_for_h then
- vim.bo[bufnr].filetype = 'ch'
+ return 'ch'
else
- vim.bo[bufnr].filetype = 'cpp'
+ return 'cpp'
+ end
+end
+
+function M.html(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if matchregex(line, [[\<DTD\s\+XHTML\s]]) then
+ return 'xhtml'
+ elseif matchregex(line, [[\c{%\s*\(extends\|block\|load\)\>\|{#\s\+]]) then
+ return 'htmldjango'
+ end
end
+ return 'html'
end
function M.idl(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 50)) do
- -- Always ignore case
- line = line:lower()
- if findany(line, { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then
- vim.bo[bufnr].filetype = 'msidl'
- return
+ if findany(line:lower(), { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then
+ return 'msidl'
end
end
- vim.bo[bufnr].filetype = 'idl'
+ return 'idl'
end
-function M.inc(path, bufnr) end
+local pascal_comments = { '^%s*{', '^%s*%(%*', '^%s*//' }
+local pascal_keywords = [[\c^\s*\%(program\|unit\|library\|uses\|begin\|procedure\|function\|const\|type\|var\)\>]]
+
+function M.inc(bufnr)
+ if vim.g.filetype_inc then
+ return vim.g.filetype_inc
+ end
+ local lines = table.concat(getlines(bufnr, 1, 3))
+ if lines:lower():find('perlscript') then
+ return 'aspperl'
+ elseif lines:find('<%%') then
+ return 'aspvbs'
+ elseif lines:find('<%?') then
+ return 'php'
+ -- Pascal supports // comments but they're vary rarely used for file
+ -- headers so assume POV-Ray
+ elseif findany(lines, { '^%s{', '^%s%(%*' }) or matchregex(lines, pascal_keywords) then
+ return 'pascal'
+ else
+ M.asm_syntax(bufnr)
+ if vim.b[bufnr].asm_syntax then
+ return vim.fn.fnameescape(vim.b[bufnr].asm_syntax)
+ else
+ return 'pov'
+ end
+ end
+end
function M.inp(bufnr)
if getlines(bufnr, 1):find('^%*') then
- vim.bo[bufnr].filetype = 'abaqus'
+ return 'abaqus'
else
for _, line in ipairs(getlines(bufnr, 1, 500)) do
if line:lower():find('^header surface data') then
- vim.bo[bufnr].filetype = 'trasys'
- return
+ return 'trasys'
+ end
+ end
+ end
+end
+
+function M.lpc(bufnr)
+ if vim.g.lpc_syntax_for_c then
+ for _, line in ipairs(getlines(bufnr, 1, 12)) do
+ if
+ findany(line, {
+ '^//',
+ '^inherit',
+ '^private',
+ '^protected',
+ '^nosave',
+ '^string',
+ '^object',
+ '^mapping',
+ '^mixed',
+ })
+ then
+ return 'lpc'
end
end
end
+ return 'c'
end
-function M.lpc(path, bufnr) end
+function M.m(bufnr)
+ if vim.g.filetype_m then
+ return vim.g.filetype_m
+ end
-function M.lprolog(path, bufnr) end
+ -- Excluding end(for|function|if|switch|while) common to Murphi
+ local octave_block_terminators =
+ [[\<end\%(_try_catch\|classdef\|enumeration\|events\|methods\|parfor\|properties\)\>]]
+ local objc_preprocessor = [[\c^\s*#\s*\%(import\|include\|define\|if\|ifn\=def\|undef\|line\|error\|pragma\)\>]]
-function M.m(path, bufnr) end
+ -- Whether we've seen a multiline comment leader
+ local saw_comment = false
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if line:find('^%s*/%*') then
+ -- /* ... */ is a comment in Objective C and Murphi, so we can't conclude
+ -- it's either of them yet, but track this as a hint in case we don't see
+ -- anything more definitive.
+ saw_comment = true
+ end
+ if line:find('^%s*//') or matchregex(line, [[\c^\s*@import\>]]) or matchregex(line, objc_preprocessor) then
+ return 'objc'
+ end
+ if
+ findany(line, { '^%s*#', '^%s*%%!' })
+ or matchregex(line, [[\c^\s*unwind_protect\>]])
+ or matchregex(line, [[\c\%(^\|;\)\s*]] .. octave_block_terminators)
+ then
+ return 'octave'
+ elseif line:find('^%s*%%') then
+ return 'matlab'
+ elseif line:find('^%s*%(%*') then
+ return 'mma'
+ elseif matchregex(line, [[\c^\s*\(\(type\|var\)\>\|--\)]]) then
+ return 'murphi'
+ end
+ end
+
+ if saw_comment then
+ -- We didn't see anything definitive, but this looks like either Objective C
+ -- or Murphi based on the comment leader. Assume the former as it is more
+ -- common.
+ return 'objc'
+ else
+ -- Default is Matlab
+ return 'matlab'
+ end
+end
-- Rely on the file to start with a comment.
-- MS message text files use ';', Sendmail files use '#' or 'dnl'
@@ -210,112 +516,273 @@ function M.mc(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 20)) do
if findany(line:lower(), { '^%s*#', '^%s*dnl' }) then
-- Sendmail .mc file
- vim.bo[bufnr].filetype = 'm4'
- return
+ return 'm4'
elseif line:find('^%s*;') then
- vim.bo[bufnr].filetype = 'msmessages'
- return
+ return 'msmessages'
end
end
-- Default: Sendmail .mc file
- vim.bo[bufnr].filetype = 'm4'
+ return 'm4'
end
-function M.mm(path, bufnr) end
+function M.mm(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 20)) do
+ if matchregex(line, [[\c^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)]]) then
+ return 'objcpp'
+ end
+ end
+ return 'nroff'
+end
function M.mms(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 20)) do
if findany(line, { '^%s*%%', '^%s*//', '^%*' }) then
- vim.bo[bufnr].filetype = 'mmix'
- return
+ return 'mmix'
elseif line:find('^%s*#') then
- vim.bo[bufnr].filetype = 'make'
- return
+ return 'make'
+ end
+ end
+ return 'mmix'
+end
+
+-- Returns true if file content looks like LambdaProlog
+local function is_lprolog(bufnr)
+ -- Skip apparent comments and blank lines, what looks like
+ -- LambdaProlog comment may be RAPID header
+ for _, line in ipairs(getlines(bufnr, 1, -1)) do
+ -- The second pattern matches a LambdaProlog comment
+ if not findany(line, { '^%s*$', '^%s*%%' }) then
+ -- The pattern must not catch a go.mod file
+ return matchregex(line, [[\c\<module\s\+\w\+\s*\.\s*\(%\|$\)]]) ~= nil
end
end
- vim.bo[bufnr].filetype = 'mmix'
end
-function M.mod(path, bufnr) end
+-- Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
+function M.mod(path, bufnr)
+ if vim.g.filetype_mod then
+ return vim.g.filetype_mod
+ elseif is_lprolog(bufnr) then
+ return 'lprolog'
+ elseif matchregex(nextnonblank(bufnr, 1), [[\%(\<MODULE\s\+\w\+\s*;\|^\s*(\*\)]]) then
+ return 'modula2'
+ elseif is_rapid(bufnr) then
+ return 'rapid'
+ elseif matchregex(path, [[\c\<go\.mod$]]) then
+ return 'gomod'
+ else
+ -- Nothing recognized, assume modsim3
+ return 'modsim3'
+ end
+end
-- This function checks if one of the first five lines start with a dot. In
--- that case it is probably an nroff file: 'filetype' is set and 1 is returned.
+-- that case it is probably an nroff file: 'filetype' is set and true is returned.
function M.nroff(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find('^%.') then
- vim.bo[bufnr].filetype = 'nroff'
- return 1
+ return true
+ end
+ end
+ return false
+end
+
+-- If the file has an extension of 't' and is in a directory 't' or 'xt' then
+-- it is almost certainly a Perl test file.
+-- If the first line starts with '#' and contains 'perl' it's probably a Perl file.
+-- (Slow test) If a file contains a 'use' statement then it is almost certainly a Perl file.
+function M.perl(path, bufnr)
+ local dirname = vim.fn.expand(path, '%:p:h:t')
+ if vim.fn.expand(dirname, '%:e') == 't' and (dirname == 't' or dirname == 'xt') then
+ return true
+ end
+ local first_line = getlines(bufnr, 1)
+ if first_line:find('^#') and first_line:lower():find('perl') then
+ return true
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 30)) do
+ if matchregex(line, [[\c^use\s\s*\k]]) then
+ return true
end
end
- return 0
+ return false
end
-function M.perl(path, bufnr) end
+function M.pl(bufnr)
+ if vim.g.filetype_pl then
+ return vim.g.filetype_pl
+ end
+ -- Recognize Prolog by specific text in the first non-empty line;
+ -- require a blank after the '%' because Perl uses "%list" and "%translate"
+ local line = nextnonblank(bufnr, 1)
+ if
+ line and line:find(':%-')
+ or matchregex(line, [[\c\<prolog\>]])
+ or findany(line, { '^%s*%%+%s', '^%s*%%+$', '^%s*/%*' })
+ then
+ return 'prolog'
+ else
+ return 'perl'
+ end
+end
-function M.pl(path, bufnr) end
+function M.pp(bufnr)
+ if vim.g.filetype_pp then
+ return vim.g.filetype_pp
+ end
+ local line = nextnonblank(bufnr, 1)
+ if findany(line, pascal_comments) or matchregex(line, pascal_keywords) then
+ return 'pascal'
+ else
+ return 'puppet'
+ end
+end
-function M.pp(path, bufnr) end
+function M.prg(bufnr)
+ if vim.g.filetype_prg then
+ return vim.g.filetype_prg
+ elseif is_rapid(bufnr) then
+ return 'rapid'
+ else
+ -- Nothing recognized, assume Clipper
+ return 'clipper'
+ end
+end
-function M.prg(path, bufnr) end
+-- This function checks for an assembly comment in the first ten lines.
+-- If not found, assume Progress.
+function M.progress_asm(bufnr)
+ if vim.g.filetype_i then
+ return vim.g.filetype_i
+ end
-function M.progress_asm(path, bufnr) end
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if line:find('^%s*;') or line:find('^/%*') then
+ return M.asm(bufnr)
+ elseif not line:find('^%s*$') or line:find('^/%*') then
+ -- Not an empty line: doesn't look like valid assembly code
+ -- or it looks like a Progress /* comment.
+ break
+ end
+ end
+ return 'progress'
+end
function M.progress_cweb(bufnr)
if vim.g.filetype_w then
- vim.bo[bufnr].filetype = vim.g.filetype_w
+ return vim.g.filetype_w
else
- if getlines(bufnr, 1):find('^&ANALYZE') or getlines(bufnr, 3):find('^&GLOBAL%-DEFINE') then
- vim.bo[bufnr].filetype = 'progress'
+ if getlines(bufnr, 1):lower():find('^&analyze') or getlines(bufnr, 3):lower():find('^&global%-define') then
+ return 'progress'
else
- vim.bo[bufnr].filetype = 'cweb'
+ return 'cweb'
end
end
end
-function M.progress_pascal(path, bufnr) end
+-- This function checks for valid Pascal syntax in the first 10 lines.
+-- Look for either an opening comment or a program start.
+-- If not found, assume Progress.
+function M.progress_pascal(bufnr)
+ if vim.g.filetype_p then
+ return vim.g.filetype_p
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if findany(line, pascal_comments) or matchregex(line, pascal_keywords) then
+ return 'pascal'
+ elseif not line:find('^%s*$') or line:find('^/%*') then
+ -- Not an empty line: Doesn't look like valid Pascal code.
+ -- Or it looks like a Progress /* comment
+ break
+ end
+ end
+ return 'progress'
+end
-function M.proto(path, bufnr) end
+-- Distinguish between "default" and Cproto prototype file.
+function M.proto(bufnr, default)
+ -- Cproto files have a comment in the first line and a function prototype in
+ -- the second line, it always ends in ";". Indent files may also have
+ -- comments, thus we can't match comments to see the difference.
+ -- IDL files can have a single ';' in the second line, require at least one
+ -- character before the ';'.
+ if getlines(bufnr, 2):find('.;$') then
+ return 'cpp'
+ else
+ return default
+ end
+end
function M.r(bufnr)
local lines = getlines(bufnr, 1, 50)
- -- TODO: \< / \> which match the beginning / end of a word
-- Rebol is easy to recognize, check for that first
- if table.concat(lines):lower():find('rebol') then
- vim.bo[bufnr].filetype = 'rebol'
- return
+ if matchregex(table.concat(lines), [[\c\<rebol\>]]) then
+ return 'rebol'
end
for _, line in ipairs(lines) do
-- R has # comments
if line:find('^%s*#') then
- vim.bo[bufnr].filetype = 'r'
- return
+ return 'r'
end
-- Rexx has /* comments */
if line:find('^%s*/%*') then
- vim.bo[bufnr].filetype = 'rexx'
- return
+ return 'rexx'
end
end
-- Nothing recognized, use user default or assume R
if vim.g.filetype_r then
- vim.bo[bufnr].filetype = vim.g.filetype_r
+ return vim.g.filetype_r
else
-- Rexx used to be the default, but R appears to be much more popular.
- vim.bo[bufnr].filetype = 'r'
+ return 'r'
end
end
function M.redif(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:lower():find('^template%-type:') then
- vim.bo[bufnr].filetype = 'redif'
+ return 'redif'
end
end
end
-function M.rules(path, bufnr) end
+local udev_rules_pattern = '^%s*udev_rules%s*=%s*"([%^"]+)/*".*'
+function M.rules(path, bufnr)
+ path = path:lower()
+ if
+ findany(path, {
+ '/etc/udev/.*%.rules$',
+ '/etc/udev/rules%.d/.*$.rules$',
+ '/usr/lib/udev/.*%.rules$',
+ '/usr/lib/udev/rules%.d/.*%.rules$',
+ '/lib/udev/.*%.rules$',
+ '/lib/udev/rules%.d/.*%.rules$',
+ })
+ then
+ return 'udevrules'
+ elseif path:find('^/etc/ufw/') then
+ -- Better than hog
+ return 'conf'
+ elseif findany(path, { '^/etc/polkit%-1/rules%.d', '/usr/share/polkit%-1/rules%.d' }) then
+ return 'javascript'
+ else
+ local ok, config_lines = pcall(vim.fn.readfile, '/etc/udev/udev.conf')
+ if not ok then
+ return 'hog'
+ end
+ local dir = vim.fn.expand(path, ':h')
+ for _, line in ipairs(config_lines) do
+ local match = line:match(udev_rules_pattern)
+ local udev_rules = line:gsub(udev_rules_pattern, match, 1)
+ if dir == udev_rules then
+ return 'udevrules'
+ end
+ end
+ return 'hog'
+ end
+end
-- This function checks the first 25 lines of file extension "sc" to resolve
-- detection between scala and SuperCollider
@@ -327,11 +794,10 @@ function M.sc(bufnr)
{ '[A-Za-z0-9]*%s:%s[A-Za-z0-9]', 'var%s<', 'classvar%s<', '%^this.*', '|%w*|', '%+%s%w*%s{', '%*ar%s' }
)
then
- vim.bo[bufnr].filetype = 'supercollider'
- return
+ return 'supercollider'
end
end
- vim.bo[bufnr].filetype = 'scala'
+ return 'scala'
end
-- This function checks the first line of file extension "scd" to resolve
@@ -341,29 +807,140 @@ function M.scd(bufnr)
local opt = [[%s+"[^"]*"]]
local line = getlines(bufnr, 1)
if findany(line, { first .. '$', first .. opt .. '$', first .. opt .. opt .. '$' }) then
- vim.bo[bufnr].filetype = 'scdoc'
+ return 'scdoc'
else
- vim.bo[bufnr].filetype = 'supercollider'
+ return 'supercollider'
end
end
-function M.sh(path, bufnr) end
+-- Also called from filetype.lua
+function M.sh(path, bufnr, name)
+ if did_filetype() or path:find(vim.g.ft_ignore_pat) then
+ -- Filetype was already detected or detection should be skipped
+ return
+ end
+
+ if matchregex(name, [[\<csh\>]]) then
+ -- Some .sh scripts contain #!/bin/csh.
+ return M.shell(path, bufnr, 'csh')
+ -- Some .sh scripts contain #!/bin/tcsh.
+ elseif matchregex(name, [[\<tcsh\>]]) then
+ return M.shell(path, bufnr, 'tcsh')
+ -- Some .sh scripts contain #!/bin/zsh.
+ elseif matchregex(name, [[\<zsh\>]]) then
+ return M.shell(path, bufnr, 'zsh')
+ elseif matchregex(name, [[\<ksh\>]]) then
+ vim.b[bufnr].is_kornshell = 1
+ vim.b[bufnr].is_bash = nil
+ vim.b[bufnr].is_sh = nil
+ elseif vim.g.bash_is_sh or matchregex(name, [[\<bash\>]]) or matchregex(name, [[\<bash2\>]]) then
+ vim.b[bufnr].is_bash = 1
+ vim.b[bufnr].is_kornshell = nil
+ vim.b[bufnr].is_sh = nil
+ elseif matchregex(name, [[\<sh\>]]) then
+ vim.b[bufnr].is_sh = 1
+ vim.b[bufnr].is_kornshell = nil
+ vim.b[bufnr].is_bash = nil
+ end
+ return M.shell(path, bufnr, 'sh')
+end
-function M.shell(path, bufnr) end
+-- For shell-like file types, check for an "exec" command hidden in a comment, as used for Tcl.
+-- Also called from scripts.vim, thus can't be local to this script. [TODO]
+function M.shell(path, bufnr, name)
+ if did_filetype() or matchregex(path, vim.g.ft_ignore_pat) then
+ -- Filetype was already detected or detection should be skipped
+ return
+ end
+ local prev_line = ''
+ for _, line in ipairs(getlines(bufnr, 2, -1)) do
+ line = line:lower()
+ if line:find('%s*exec%s') and not prev_line:find('^%s*#.*\\$') then
+ -- Found an "exec" line after a comment with continuation
+ local n = line:gsub('%s*exec%s+([^ ]*/)?', '', 1)
+ if matchregex(n, [[\c\<tclsh\|\<wish]]) then
+ return 'tcl'
+ end
+ end
+ prev_line = line
+ end
+ return name
+end
function M.sql(bufnr)
if vim.g.filetype_sql then
- vim.bo[bufnr].filetype = vim.g.filetype_sql
+ return vim.g.filetype_sql
else
- vim.bo[bufnr].filetype = 'sql'
+ return 'sql'
end
end
-function M.src(path, bufnr) end
+-- Determine if a *.src file is Kuka Robot Language
+function M.src(bufnr)
+ if vim.g.filetype_src then
+ return vim.g.filetype_src
+ end
+ local line = nextnonblank(bufnr, 1)
+ if matchregex(line, [[\c\v^\s*%(\&\w+|%(global\s+)?def%(fct)?>)]]) then
+ return 'krl'
+ end
+end
-function M.sys(path, bufnr) end
+function M.sys(bufnr)
+ if vim.g.filetype_sys then
+ return vim.g.filetype_sys
+ elseif is_rapid(bufnr) then
+ return 'rapid'
+ else
+ return 'bat'
+ end
+end
-function M.tex(path, bufnr) end
+-- Choose context, plaintex, or tex (LaTeX) based on these rules:
+-- 1. Check the first line of the file for "%&<format>".
+-- 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords.
+-- 3. Default to "plain" or to g:tex_flavor, can be set in user's vimrc.
+function M.tex(path, bufnr)
+ local format = getlines(bufnr, 1):find('^%%&%s*(%a+)')
+ if format then
+ format = format:lower():gsub('pdf', '', 1)
+ if format == 'tex' then
+ return 'tex'
+ elseif format == 'plaintex' then
+ return 'plaintex'
+ end
+ elseif path:lower():find('tex/context/.*/.*%.tex') then
+ return 'context'
+ else
+ local lpat = [[documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>]]
+ local cpat =
+ [[start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>]]
+
+ for i, l in ipairs(getlines(bufnr, 1, 1000)) do
+ -- Find first non-comment line
+ if not l:find('^%s*%%%S') then
+ -- Check the next thousand lines for a LaTeX or ConTeXt keyword.
+ for _, line in ipairs(getlines(bufnr, i + 1, i + 1000)) do
+ local lpat_match, cpat_match = matchregex(line, [[\c^\s*\\\%(]] .. lpat .. [[\)\|^\s*\\\(]] .. cpat .. [[\)]])
+ if lpat_match then
+ return 'tex'
+ elseif cpat_match then
+ return 'context'
+ end
+ end
+ end
+ end
+ -- TODO: add AMSTeX, RevTex, others?
+ if not vim.g.tex_flavor or vim.g.tex_flavor == 'plain' then
+ return 'plaintex'
+ elseif vim.g.tex_flavor == 'context' then
+ return 'context'
+ else
+ -- Probably LaTeX
+ return 'tex'
+ end
+ end
+end
-- Determine if a *.tf file is TF mud client or terraform
function M.tf(bufnr)
@@ -371,45 +948,39 @@ function M.tf(bufnr)
-- Assume terraform file on a non-empty line (not whitespace-only)
-- and when the first non-whitespace character is not a ; or /
if not line:find('^%s*$') and not line:find('^%s*[;/]') then
- vim.bo[bufnr].filetype = 'terraform'
- return
+ return 'terraform'
end
end
- vim.bo[bufnr].filetype = 'tf'
+ return 'tf'
end
function M.xml(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ local is_docbook4 = line:find('<!DOCTYPE.*DocBook')
line = line:lower()
- local is_docbook4 = line:find('<!doctype.*docbook')
local is_docbook5 = line:find([[ xmlns="http://docbook.org/ns/docbook"]])
if is_docbook4 or is_docbook5 then
vim.b[bufnr].docbk_type = 'xml'
vim.b[bufnr].docbk_ver = is_docbook4 and 4 or 5
- vim.bo[bufnr].filetype = 'docbk'
- return
+ return 'docbk'
end
if line:find([[xmlns:xbl="http://www.mozilla.org/xbl"]]) then
- vim.bo[bufnr].filetype = 'xbl'
- return
+ return 'xbl'
end
end
- vim.bo[bufnr].filetype = 'xml'
+ return 'xml'
end
function M.y(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 100)) do
if line:find('^%s*%%') then
- vim.bo[bufnr].filetype = 'yacc'
- return
+ return 'yacc'
end
- -- TODO: in the Vim regex, \> is used to match the end of the word after "class",
- -- can this be omitted?
- if findany(line, { '^%s*#', '^%class', '^%s*#%s*include' }) then
- vim.bo[bufnr].filetype = 'racc'
+ if matchregex(line, [[\c^\s*\(#\|class\>\)]]) and not line:lower():find('^%s*#%s*include') then
+ return 'racc'
end
end
- vim.bo[bufnr].filetype = 'yacc'
+ return 'yacc'
end
-- luacheck: pop
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index e99a7c282c..dac2860690 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,5 +1,3 @@
-local if_nil = vim.F.if_nil
-
local default_handlers = require('vim.lsp.handlers')
local log = require('vim.lsp.log')
local lsp_rpc = require('vim.lsp.rpc')
@@ -8,11 +6,16 @@ local util = require('vim.lsp.util')
local sync = require('vim.lsp.sync')
local vim = vim
-local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option =
- vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option
+local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
+ vim.api.nvim_err_writeln,
+ vim.api.nvim_buf_get_lines,
+ vim.api.nvim_command,
+ vim.api.nvim_buf_get_option,
+ vim.api.nvim_exec_autocmds
local uv = vim.loop
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
local validate = vim.validate
+local if_nil = vim.F.if_nil
local lsp = {
protocol = protocol,
@@ -867,15 +870,27 @@ function lsp.start_client(config)
pcall(config.on_exit, code, signal, client_id)
end
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[client_id] then
+ vim.schedule(function()
+ nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
+
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ vim.diagnostic.reset(namespace, bufnr)
+ end)
+
+ client_ids[client_id] = nil
+ end
+ end
+
active_clients[client_id] = nil
uninitialized_clients[client_id] = nil
- lsp.diagnostic.reset(client_id, all_buffer_active_clients)
changetracking.reset(client_id)
- for _, client_ids in pairs(all_buffer_active_clients) do
- client_ids[client_id] = nil
- end
-
if code ~= 0 or (signal ~= 0 and signal ~= 15) then
local msg = string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal)
vim.schedule(function()
@@ -1173,12 +1188,6 @@ function lsp.start_client(config)
---
---@param force (bool, optional)
function client.stop(force)
- lsp.diagnostic.reset(client_id, all_buffer_active_clients)
- changetracking.reset(client_id)
- for _, client_ids in pairs(all_buffer_active_clients) do
- client_ids[client_id] = nil
- end
-
local handle = rpc.handle
if handle:is_closing() then
return
@@ -1213,6 +1222,13 @@ function lsp.start_client(config)
---@param bufnr (number) Buffer number
function client._on_attach(bufnr)
text_document_did_open_handler(bufnr, client)
+
+ nvim_exec_autocmds('LspAttach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client.id },
+ })
+
if config.on_attach then
-- TODO(ashkan) handle errors.
pcall(config.on_attach, client, bufnr)
@@ -1359,6 +1375,12 @@ function lsp.buf_detach_client(bufnr, client_id)
return
end
+ nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
+
changetracking.reset_buf(client, bufnr)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
@@ -1435,11 +1457,29 @@ function lsp.stop_client(client_id, force)
end
end
---- Gets all active clients.
+--- Get active clients.
---
----@returns Table of |vim.lsp.client| objects
-function lsp.get_active_clients()
- return vim.tbl_values(active_clients)
+---@param filter (table|nil) A table with key-value pairs used to filter the
+--- returned clients. The available keys are:
+--- - id (number): Only return clients with the given id
+--- - bufnr (number): Only return clients attached to this buffer
+--- - name (string): Only return clients with the given name
+---@returns (table) List of |vim.lsp.client| objects
+function lsp.get_active_clients(filter)
+ validate({ filter = { filter, 't', true } })
+
+ filter = filter or {}
+
+ local clients = {}
+
+ local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {}) or active_clients
+ for client_id in pairs(t) do
+ local client = active_clients[client_id]
+ if (filter.id == nil or client.id == filter.id) and (filter.name == nil or client.name == filter.name) then
+ clients[#clients + 1] = client
+ end
+ end
+ return clients
end
function lsp._vim_exit_handler()
@@ -1814,12 +1854,13 @@ end
--- is a |vim.lsp.client| object.
---
---@param bufnr (optional, number): Buffer handle, or 0 for current
+---@returns (table) Table of (client_id, client) pairs
+---@deprecated Use |vim.lsp.get_active_clients()| instead.
function lsp.buf_get_clients(bufnr)
- bufnr = resolve_bufnr(bufnr)
local result = {}
- for_each_buffer_client(bufnr, function(client, client_id)
- result[client_id] = client
- end)
+ for _, client in ipairs(lsp.get_active_clients({ bufnr = resolve_bufnr(bufnr) })) do
+ result[client.id] = client
+ end
return result
end
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index b0bf2c6e5b..bcfaecdfcc 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -63,26 +63,45 @@ function M.hover()
request('textDocument/hover', params)
end
+---@private
+local function request_with_options(name, params, options)
+ local req_handler
+ if options then
+ req_handler = function(err, result, ctx, config)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local handler = client.handlers[name] or vim.lsp.handlers[name]
+ handler(err, result, ctx, vim.tbl_extend('force', config or {}, options))
+ end
+ end
+ request(name, params, req_handler)
+end
+
--- Jumps to the declaration of the symbol under the cursor.
---@note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
---
-function M.declaration()
+---@param options table|nil additional options
+--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
+function M.declaration(options)
local params = util.make_position_params()
- request('textDocument/declaration', params)
+ request_with_options('textDocument/declaration', params, options)
end
--- Jumps to the definition of the symbol under the cursor.
---
-function M.definition()
+---@param options table|nil additional options
+--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
+function M.definition(options)
local params = util.make_position_params()
- request('textDocument/definition', params)
+ request_with_options('textDocument/definition', params, options)
end
--- Jumps to the definition of the type of the symbol under the cursor.
---
-function M.type_definition()
+---@param options table|nil additional options
+--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
+function M.type_definition(options)
local params = util.make_position_params()
- request('textDocument/typeDefinition', params)
+ request_with_options('textDocument/typeDefinition', params, options)
end
--- Lists all the implementations for the symbol under the cursor in the
@@ -158,20 +177,15 @@ end
--- - bufnr (number|nil):
--- Restrict formatting to the clients attached to the given buffer, defaults to the current
--- buffer (0).
+---
--- - filter (function|nil):
---- Predicate to filter clients used for formatting. Receives the list of clients attached
---- to bufnr as the argument and must return the list of clients on which to request
---- formatting. Example:
+--- Predicate used to filter clients. Receives a client as argument and must return a
+--- boolean. Clients matching the predicate are included. Example:
---
--- <pre>
--- -- Never request typescript-language-server for formatting
--- vim.lsp.buf.format {
---- filter = function(clients)
---- return vim.tbl_filter(
---- function(client) return client.name ~= "tsserver" end,
---- clients
---- )
---- end
+--- filter = function(client) return client.name ~= "tsserver" end
--- }
--- </pre>
---
@@ -188,18 +202,14 @@ end
function M.format(options)
options = options or {}
local bufnr = options.bufnr or vim.api.nvim_get_current_buf()
- local clients = vim.lsp.buf_get_clients(bufnr)
+ local clients = vim.lsp.get_active_clients({
+ id = options.id,
+ bufnr = bufnr,
+ name = options.name,
+ })
if options.filter then
- clients = options.filter(clients)
- elseif options.id then
- clients = vim.tbl_filter(function(client)
- return client.id == options.id
- end, clients)
- elseif options.name then
- clients = vim.tbl_filter(function(client)
- return client.name == options.name
- end, clients)
+ clients = vim.tbl_filter(options.filter, clients)
end
clients = vim.tbl_filter(function(client)
@@ -367,23 +377,20 @@ end
--- name using |vim.ui.input()|.
---@param options table|nil additional options
--- - filter (function|nil):
---- Predicate to filter clients used for rename.
---- Receives the attached clients as argument and must return a list of
---- clients.
+--- Predicate used to filter clients. Receives a client as argument and
+--- must return a boolean. Clients matching the predicate are included.
--- - name (string|nil):
--- Restrict clients used for rename to ones where client.name matches
--- this field.
function M.rename(new_name, options)
options = options or {}
local bufnr = options.bufnr or vim.api.nvim_get_current_buf()
- local clients = vim.lsp.buf_get_clients(bufnr)
-
+ local clients = vim.lsp.get_active_clients({
+ bufnr = bufnr,
+ name = options.name,
+ })
if options.filter then
- clients = options.filter(clients)
- elseif options.name then
- clients = vim.tbl_filter(function(client)
- return client.name == options.name
- end, clients)
+ clients = vim.tbl_filter(options.filter, clients)
end
-- Clients must at least support rename, prepareRename is optional
@@ -844,7 +851,8 @@ function M.code_action(options)
end
local context = options.context or {}
if not context.diagnostics then
- context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
+ local bufnr = vim.api.nvim_get_current_buf()
+ context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
end
local params = util.make_range_params()
params.context = context
@@ -870,7 +878,8 @@ function M.range_code_action(context, start_pos, end_pos)
validate({ context = { context, 't', true } })
context = context or {}
if not context.diagnostics then
- context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
+ local bufnr = vim.api.nvim_get_current_buf()
+ context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
end
local params = util.make_given_range_params(start_pos, end_pos)
params.context = context
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index b3a253c118..61cc89dcac 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -327,18 +327,20 @@ M['textDocument/hover'] = M.hover
---@param result (table) result of LSP method; a location or a list of locations.
---@param ctx (table) table containing the context of the request, including the method
---(`textDocument/definition` can return `Location` or `Location[]`
-local function location_handler(_, result, ctx, _)
+local function location_handler(_, result, ctx, config)
if result == nil or vim.tbl_isempty(result) then
local _ = log.info() and log.info(ctx.method, 'No location found')
return nil
end
local client = vim.lsp.get_client_by_id(ctx.client_id)
+ config = config or {}
+
-- textDocument/definition can return Location or Location[]
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
if vim.tbl_islist(result) then
- util.jump_to_location(result[1], client.offset_encoding)
+ util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
if #result > 1 then
vim.fn.setqflist({}, ' ', {
@@ -348,7 +350,7 @@ local function location_handler(_, result, ctx, _)
api.nvim_command('botright copen')
end
else
- util.jump_to_location(result, client.offset_encoding)
+ util.jump_to_location(result, client.offset_encoding, config.reuse_win)
end
end
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 74714ebc6b..bf8fe0932e 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -17,7 +17,8 @@ function M.check()
local log_path = vim.lsp.get_log_path()
report_info(string.format('Log path: %s', log_path))
- local log_size = vim.loop.fs_stat(log_path).size
+ local log_file = vim.loop.fs_stat(log_path)
+ local log_size = log_file and log_file.size or 0
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
report_fn(string.format('Log size: %d KB', log_size / 1000))
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 2dcafc92bc..ad2498fb6f 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -4,6 +4,8 @@ local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
+local is_win = uv.os_uname().version:find('Windows')
+
---@private
--- Checks whether a given path exists and is a directory.
---@param filename (string) path to check
@@ -321,7 +323,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
local spawn_params = {
args = cmd_args,
stdio = { stdin, stdout, stderr },
- detached = true,
+ detached = not is_win,
}
if extra_spawn_params then
spawn_params.cwd = extra_spawn_params.cwd
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index e8a8e06f46..63e9342b1a 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -684,6 +684,16 @@ function M.text_document_completion_list_to_complete_items(result, prefix)
return matches
end
+---@private
+--- Like vim.fn.bufwinid except it works across tabpages.
+local function bufwinid(bufnr)
+ for _, win in ipairs(api.nvim_list_wins()) do
+ if api.nvim_win_get_buf(win) == bufnr then
+ return win
+ end
+ end
+end
+
--- Rename old_fname to new_fname
---
---@param opts (table)
@@ -708,10 +718,9 @@ function M.rename(old_fname, new_fname, opts)
assert(ok, err)
local newbuf = vim.fn.bufadd(new_fname)
- for _, win in pairs(api.nvim_list_wins()) do
- if api.nvim_win_get_buf(win) == oldbuf then
- api.nvim_win_set_buf(win, newbuf)
- end
+ local win = bufwinid(oldbuf)
+ if win then
+ api.nvim_win_set_buf(win, newbuf)
end
api.nvim_buf_delete(oldbuf, { force = true })
end
@@ -1004,8 +1013,9 @@ end
---
---@param location table (`Location`|`LocationLink`)
---@param offset_encoding string utf-8|utf-16|utf-32 (required)
+---@param reuse_win boolean Jump to existing window if buffer is already opened.
---@returns `true` if the jump succeeded
-function M.jump_to_location(location, offset_encoding)
+function M.jump_to_location(location, offset_encoding, reuse_win)
-- location may be Location or LocationLink
local uri = location.uri or location.targetUri
if uri == nil then
@@ -1024,8 +1034,13 @@ function M.jump_to_location(location, offset_encoding)
vim.fn.settagstack(vim.fn.win_getid(), { items = items }, 't')
--- Jump to new location (adjusting for UTF-16 encoding of characters)
- api.nvim_set_current_buf(bufnr)
- api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ local win = reuse_win and bufwinid(bufnr)
+ if win then
+ api.nvim_set_current_win(win)
+ else
+ api.nvim_set_current_buf(bufnr)
+ api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ end
local range = location.range or location.targetSelectionRange
local row = range.start.line
local col = get_line_byte_from_position(bufnr, range.start, offset_encoding)
@@ -1462,7 +1477,7 @@ function M.open_floating_preview(contents, syntax, opts)
})
opts = opts or {}
opts.wrap = opts.wrap ~= false -- wrapping by default
- opts.stylize_markdown = opts.stylize_markdown ~= false
+ opts.stylize_markdown = opts.stylize_markdown ~= false and vim.g.syntax_on ~= nil
opts.focus = opts.focus ~= false
opts.close_events = opts.close_events or { 'CursorMoved', 'CursorMovedI', 'InsertCharPre' }
@@ -1676,7 +1691,7 @@ end
---
---@param items (table) list of items
function M.set_loclist(items, win_id)
- vim.api.nvim_echo({ { 'vim.lsp.util.set_loclist is deprecated. See :h deprecated', 'WarningMsg' } }, true, {})
+ vim.deprecate('vim.lsp.util.set_loclist', 'setloclist', '0.8')
vim.fn.setloclist(win_id or 0, {}, ' ', {
title = 'Language Server',
items = items,
@@ -1690,7 +1705,7 @@ end
---
---@param items (table) list of items
function M.set_qflist(items)
- vim.api.nvim_echo({ { 'vim.lsp.util.set_qflist is deprecated. See :h deprecated', 'WarningMsg' } }, true, {})
+ vim.deprecate('vim.lsp.util.set_qflist', 'setqflist', '0.8')
vim.fn.setqflist({}, ' ', {
title = 'Language Server',
items = items,
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 2157112d2f..57d8c5fd21 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -519,17 +519,11 @@ local function tree_contains(tree, range)
local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4])
- if start_fits and end_fits then
- return true
- end
-
- return false
+ return start_fits and end_fits
end
--- Determines whether {range} is contained in this language tree
---
---- This goes down the tree to recursively check children.
----
---@param range A range, that is a `{ start_line, start_col, end_line, end_col }` table.
function LanguageTree:contains(range)
for _, tree in pairs(self._trees) do