diff options
author | Bram Moolenaar <Bram@vim.org> | 2022-08-15 18:51:32 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2022-08-15 18:51:32 +0100 |
commit | e1f3fd1d02e3f5fe6d2b6d82687c6846b8e500f8 (patch) | |
tree | f00150bb6e9c2813a69adef48cea5d43c0680c32 /runtime/autoload | |
parent | 5a4fff4d948cd12a5cf5f637ad2c561815a77d8e (diff) |
Update runtime files
Diffstat (limited to 'runtime/autoload')
-rw-r--r-- | runtime/autoload/context.vim | 267 | ||||
-rw-r--r-- | runtime/autoload/typeset.vim | 233 |
2 files changed, 322 insertions, 178 deletions
diff --git a/runtime/autoload/context.vim b/runtime/autoload/context.vim index 254d710c01..e42b99e2e9 100644 --- a/runtime/autoload/context.vim +++ b/runtime/autoload/context.vim @@ -1,184 +1,95 @@ -" Language: ConTeXt typesetting engine -" Maintainer: Nicola Vitacolonna <nvitacolonna@gmail.com> -" Latest Revision: 2016 Oct 21 - -let s:keepcpo= &cpo -set cpo&vim - -" Helper functions {{{ -function! s:context_echo(message, mode) - redraw - echo "\r" - execute 'echohl' a:mode - echomsg '[ConTeXt]' a:message - echohl None -endf - -function! s:sh() - return has('win32') || has('win64') || has('win16') || has('win95') - \ ? ['cmd.exe', '/C'] - \ : ['/bin/sh', '-c'] -endfunction - -" For backward compatibility -if exists('*win_getid') - - function! s:win_getid() - return win_getid() - endf - - function! s:win_id2win(winid) - return win_id2win(a:winid) - endf - -else - - function! s:win_getid() - return winnr() - endf - - function! s:win_id2win(winnr) - return a:winnr - endf - -endif -" }}} - -" ConTeXt jobs {{{ -if has('job') - - let g:context_jobs = [] - - " Print the status of ConTeXt jobs - function! context#job_status() - let l:jobs = filter(g:context_jobs, 'job_status(v:val) == "run"') - let l:n = len(l:jobs) - call s:context_echo( - \ 'There '.(l:n == 1 ? 'is' : 'are').' '.(l:n == 0 ? 'no' : l:n) - \ .' job'.(l:n == 1 ? '' : 's').' running' - \ .(l:n == 0 ? '.' : ' (' . join(l:jobs, ', ').').'), - \ 'ModeMsg') - endfunction - - " Stop all ConTeXt jobs - function! context#stop_jobs() - let l:jobs = filter(g:context_jobs, 'job_status(v:val) == "run"') - for job in l:jobs - call job_stop(job) - endfor - sleep 1 - let l:tmp = [] - for job in l:jobs - if job_status(job) == "run" - call add(l:tmp, job) - endif - endfor - let g:context_jobs = l:tmp - if empty(g:context_jobs) - call s:context_echo('Done. No jobs running.', 'ModeMsg') +vim9script + +# Language: ConTeXt typesetting engine +# Maintainer: Nicola Vitacolonna <nvitacolonna@gmail.com> +# Former Maintainers: Nikolai Weibull <now@bitwi.se> +# Latest Revision: 2022 Aug 12 + +# Typesetting {{{ +import autoload './typeset.vim' + +export def ConTeXtCmd(path: string): list<string> + return ['mtxrun', '--script', 'context', '--nonstopmode', '--autogenerate', path] +enddef + +export def Typeset(bufname: string, env = {}, Cmd = ConTeXtCmd): bool + return typeset.TypesetBuffer(bufname, Cmd, env, 'ConTeXt') +enddef + +export def JobStatus() + typeset.JobStatus('ConTeXt') +enddef + +export def StopJobs() + typeset.StopJobs('ConTeXt') +enddef + +export def Log(bufname: string) + execute 'edit' typeset.LogPath(bufname) +enddef +# }}} + +# Completion {{{ +def BinarySearch(base: string, keywords: list<string>): list<string> + const pat = '^' .. base + const len = len(keywords) + var res = [] + var lft = 0 + var rgt = len + + # Find the leftmost index matching base + while lft < rgt + var i = (lft + rgt) / 2 + if keywords[i] < base + lft = i + 1 else - call s:context_echo('There are still some jobs running. Please try again.', 'WarningMsg') + rgt = i endif - endfunction + endwhile - function! context#callback(path, job, status) - if index(g:context_jobs, a:job) != -1 && job_status(a:job) != 'run' " just in case - call remove(g:context_jobs, index(g:context_jobs, a:job)) + while lft < len && keywords[lft] =~ pat + add(res, keywords[lft]) + lft += 1 + endwhile + + return res +enddef + +var isMetaPostBlock = false + +var MP_KEYWORDS: list<string> = [] +var CTX_KEYWORDS: list<string> = [] + +# Complete only MetaPost keywords in MetaPost blocks, and complete only +# ConTeXt keywords otherwise. +export def Complete(findstart: number, base: string): any + if findstart == 1 + if len(synstack(line("."), 1)) > 0 && synIDattr(synstack(line("."), 1)[0], "name") ==# 'contextMPGraphic' + isMetaPostBlock = true + return match(getline('.'), '\S\+\%' .. col('.') .. 'c') + endif + + # Complete only \commands starting with a backslash + isMetaPostBlock = false + var pos = match(getline('.'), '\\\zs\S\+\%' .. col('.') .. 'c') + return (pos == -1) ? -3 : pos + endif + + if isMetaPostBlock + if empty(MP_KEYWORDS) + MP_KEYWORDS = sort(syntaxcomplete#OmniSyntaxList(['mf\w\+', 'mp\w\+'])) endif - call s:callback(a:path, a:job, a:status) - endfunction - - function! context#close_cb(channel) - call job_status(ch_getjob(a:channel)) " Trigger exit_cb's callback for faster feedback - endfunction - - function! s:typeset(path) - call add(g:context_jobs, - \ job_start(add(s:sh(), context#command() . ' ' . shellescape(fnamemodify(a:path, ":t"))), { - \ 'close_cb' : 'context#close_cb', - \ 'exit_cb' : function(get(b:, 'context_callback', get(g:, 'context_callback', 'context#callback')), - \ [a:path]), - \ 'in_io' : 'null' - \ })) - endfunction - -else " No jobs - - function! context#job_status() - call s:context_echo('Not implemented', 'WarningMsg') - endfunction! - - function! context#stop_jobs() - call s:context_echo('Not implemented', 'WarningMsg') - endfunction - - function! context#callback(path, job, status) - call s:callback(a:path, a:job, a:status) - endfunction - - function! s:typeset(path) - execute '!' . context#command() . ' ' . shellescape(fnamemodify(a:path, ":t")) - call call(get(b:, 'context_callback', get(g:, 'context_callback', 'context#callback')), - \ [a:path, 0, v:shell_error]) - endfunction - -endif " has('job') - -function! s:callback(path, job, status) abort - if a:status < 0 " Assume the job was terminated - return + return BinarySearch(base, MP_KEYWORDS) endif - " Get info about the current window - let l:winid = s:win_getid() " Save window id - let l:efm = &l:errorformat " Save local errorformat - let l:cwd = fnamemodify(getcwd(), ":p") " Save local working directory - " Set errorformat to parse ConTeXt errors - execute 'setl efm=' . escape(b:context_errorformat, ' ') - try " Set cwd to expand error file correctly - execute 'lcd' fnameescape(fnamemodify(a:path, ':h')) - catch /.*/ - execute 'setl efm=' . escape(l:efm, ' ') - throw v:exception - endtry - try - execute 'cgetfile' fnameescape(fnamemodify(a:path, ':r') . '.log') - botright cwindow - finally " Restore cwd and errorformat - execute s:win_id2win(l:winid) . 'wincmd w' - execute 'lcd ' . fnameescape(l:cwd) - execute 'setl efm=' . escape(l:efm, ' ') - endtry - if a:status == 0 - call s:context_echo('Success!', 'ModeMsg') - else - call s:context_echo('There are errors. ', 'ErrorMsg') + + if empty(CTX_KEYWORDS) + CTX_KEYWORDS = sort(syntaxcomplete#OmniSyntaxList([ + 'context\w\+', 'texAleph', 'texEtex', 'texLuatex', 'texOmega', + 'texPdftex', 'texTex', 'texXeTeX' + ])) endif -endfunction - -function! context#command() - return get(b:, 'context_mtxrun', get(g:, 'context_mtxrun', 'mtxrun')) - \ . ' --script context --autogenerate --nonstopmode' - \ . ' --synctex=' . (get(b:, 'context_synctex', get(g:, 'context_synctex', 0)) ? '1' : '0') - \ . ' ' . get(b:, 'context_extra_options', get(g:, 'context_extra_options', '')) -endfunction - -" Accepts an optional path (useful for big projects, when the file you are -" editing is not the project's root document). If no argument is given, uses -" the path of the current buffer. -function! context#typeset(...) abort - let l:path = fnamemodify(strlen(a:000[0]) > 0 ? a:1 : expand("%"), ":p") - let l:cwd = fnamemodify(getcwd(), ":p") " Save local working directory - call s:context_echo('Typesetting...', 'ModeMsg') - execute 'lcd' fnameescape(fnamemodify(l:path, ":h")) - try - call s:typeset(l:path) - finally " Restore local working directory - execute 'lcd ' . fnameescape(l:cwd) - endtry -endfunction! -"}}} - -let &cpo = s:keepcpo -unlet s:keepcpo - -" vim: sw=2 fdm=marker + return BinarySearch(base, CTX_KEYWORDS) +enddef +# }}} + +# vim: sw=2 fdm=marker diff --git a/runtime/autoload/typeset.vim b/runtime/autoload/typeset.vim new file mode 100644 index 0000000000..35cf17ba87 --- /dev/null +++ b/runtime/autoload/typeset.vim @@ -0,0 +1,233 @@ +vim9script + +# Language: Generic TeX typesetting engine +# Maintainer: Nicola Vitacolonna <nvitacolonna@gmail.com> +# Latest Revision: 2022 Aug 12 + +# Constants and helpers {{{ +const SLASH = !exists("+shellslash") || &shellslash ? '/' : '\' + +def Echo(msg: string, mode: string, label: string) + redraw + echo "\r" + execute 'echohl' mode + echomsg printf('[%s] %s', label, msg) + echohl None +enddef + +def EchoMsg(msg: string, label = 'Notice') + Echo(msg, 'ModeMsg', label) +enddef + +def EchoWarn(msg: string, label = 'Warning') + Echo(msg, 'WarningMsg', label) +enddef + +def EchoErr(msg: string, label = 'Error') + Echo(msg, 'ErrorMsg', label) +enddef +# }}} + +# Track jobs {{{ +var running_jobs = {} # Dictionary of job IDs of jobs currently executing + +def AddJob(label: string, j: job) + if !has_key(running_jobs, label) + running_jobs[label] = [] + endif + + add(running_jobs[label], j) +enddef + +def RemoveJob(label: string, j: job) + if has_key(running_jobs, label) && index(running_jobs[label], j) != -1 + remove(running_jobs[label], index(running_jobs[label], j)) + endif +enddef + +def GetRunningJobs(label: string): list<job> + return has_key(running_jobs, label) ? running_jobs[label] : [] +enddef +# }}} + +# Callbacks {{{ +def ProcessOutput(qfid: number, wd: string, efm: string, ch: channel, msg: string) + # Make sure the quickfix list still exists + if getqflist({'id': qfid}).id != qfid + EchoErr("Quickfix list not found, stopping the job") + call job_stop(ch_getjob(ch)) + return + endif + + # Make sure the working directory is correct + silent execute "lcd" wd + setqflist([], 'a', {'id': qfid, 'lines': [msg], 'efm': efm}) + silent lcd - +enddef + +def CloseCb(ch: channel) + job_status(ch_getjob(ch)) # Trigger exit_cb's callback +enddef + +def ExitCb(label: string, jobid: job, exitStatus: number) + RemoveJob(label, jobid) + + if exitStatus == 0 + botright cwindow + EchoMsg('Success!', label) + elseif exitStatus < 0 + EchoWarn('Job terminated', label) + else + botright copen + wincmd p + EchoWarn('There are errors.', label) + endif +enddef +# }}} + +# Create a new empty quickfix list at the end of the stack and return its id {{{ +def NewQuickfixList(path: string): number + if setqflist([], ' ', {'nr': '$', 'title': path}) == -1 + return -1 + endif + + return getqflist({'nr': '$', 'id': 0}).id +enddef +# }}} + +# Public interface {{{ +# When a TeX document is split into several source files, each source file +# may contain a "magic line" specifiying the "root" file, e.g.: +# +# % !TEX root = main.tex +# +# Using this line, Vim can know which file to typeset even if the current +# buffer is different from main.tex. +# +# This function searches for the magic line in the first ten lines of the +# given buffer, and returns the full path of the root document. +# +# NOTE: the value of "% !TEX root" *must* be a relative path. +export def FindRootDocument(bufname: string = bufname("%")): string + const bufnr = bufnr(bufname) + + if !bufexists(bufnr) + return bufname + endif + + var rootpath = fnamemodify(bufname(bufnr), ':p') + + # Search for magic line `% !TEX root = ...` in the first ten lines + const header = getbufline(bufnr, 1, 10) + const idx = match(header, '^\s*%\s\+!TEX\s\+root\s*=\s*\S') + if idx > -1 + const main = matchstr(header[idx], '!TEX\s\+root\s*=\s*\zs.*$') + rootpath = simplify(fnamemodify(rootpath, ":h") .. SLASH .. main) + endif + + return rootpath +enddef + +export def LogPath(bufname: string): string + const logfile = FindRootDocument(bufname) + return fnamemodify(logfile, ":r") .. ".log" +enddef + +# Typeset the specified path +# +# Parameters: +# label: a descriptive string used in messages to identify the kind of job +# Cmd: a function that takes the path of a document and returns the typesetting command +# path: the path of the document to be typeset. To avoid ambiguities, pass a *full* path. +# efm: the error format string to parse the output of the command. +# env: environment variables for the process (passed to job_start()) +# +# Returns: +# true if the job is started successfully; +# false otherwise. +export def Typeset( + label: string, + Cmd: func(string): list<string>, + path: string, + efm: string, + env: dict<string> = {} +): bool + var fp = fnamemodify(path, ":p") + var wd = fnamemodify(fp, ":h") + var qfid = NewQuickfixList(fp) + + if qfid == -1 + EchoErr('Could not create quickfix list', label) + return false + endif + + if !filereadable(fp) + EchoErr(printf('File not readable: %s', fp), label) + return false + endif + + var jobid = job_start(Cmd(path), { + env: env, + cwd: wd, + in_io: "null", + callback: (c, m) => ProcessOutput(qfid, wd, efm, c, m), + close_cb: CloseCb, + exit_cb: (j, e) => ExitCb(label, j, e), + }) + + if job_status(jobid) ==# "fail" + EchoErr("Failed to start job", label) + return false + endif + + AddJob(label, jobid) + + EchoMsg('Typesetting...', label) + + return true +enddef + +export def JobStatus(label: string) + EchoMsg('Jobs still running: ' .. string(len(GetRunningJobs(label))), label) +enddef + +export def StopJobs(label: string) + for job in GetRunningJobs(label) + job_stop(job) + endfor + + EchoMsg('Done.', label) +enddef + +# Typeset the specified buffer +# +# Parameters: +# name: a buffer's name. this may be empty to indicate the current buffer. +# cmd: a function that takes the path of a document and returns the typesetting command +# label: a descriptive string used in messages to identify the kind of job +# env: environment variables for the process (passed to job_start()) +# +# Returns: +# true if the job is started successfully; +# false otherwise. +export def TypesetBuffer( + name: string, + Cmd: func(string): list<string>, + env = {}, + label = 'Typeset' +): bool + const bufname = bufname(name) + + if empty(bufname) + EchoErr('Please save the buffer first.', label) + return false + endif + + const efm = getbufvar(bufnr(bufname), "&efm") + const rootpath = FindRootDocument(bufname) + + return Typeset('ConTeXt', Cmd, rootpath, efm, env) +enddef +# }}} + +# vim: sw=2 fdm=marker |