Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ characters they both highlight.
values, plot styles, plot-element modifiers, terminal names, built-in
functions and variables, datablocks, macros, numbers with unit suffixes,
and both string flavours.
- Sensible buffer-local editor settings (comments, `iskeyword`,
`matchpairs`), all undone by `b:undo_ftplugin`.
- Sensible buffer-local editor settings (comments, `iskeyword`), all undone
by `b:undo_ftplugin`.
- No commands, no mappings, no global options. Nothing to configure.
- Pure Vim script.

Expand Down Expand Up @@ -110,8 +110,6 @@ The ftplugin sets a few buffer-local options:
code does not auto-wrap.
- `iskeyword+=_`, because gnuplot identifiers and its built-in variables
(`GPVAL_TERM`, `STATS_mean_y`) contain underscores.
- `matchpairs+=<:>`, so `%` jumps between the brackets in
`set xrange [0:1]` style pairs.

All of it is registered in `b:undo_ftplugin`, so `:setfiletype` away from
gnuplot restores your defaults.
Expand Down
3 changes: 0 additions & 3 deletions doc/gnuplot.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ The ftplugin sets a few buffer-local options:
'iskeyword' gains `_`, because gnuplot identifiers and its built-in
variables (`GPVAL_TERM`, `STATS_mean_y`) contain underscores.

'matchpairs' gains `<:>`, so |%| jumps between the brackets in
`set xrange [0:1]` style pairs.

Everything is registered in |b:undo_ftplugin|, so leaving the filetype
restores your defaults.

Expand Down
5 changes: 1 addition & 4 deletions ftplugin/gnuplot.vim
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ setlocal formatoptions+=croql
" contain underscores.
setlocal iskeyword+=_

" `set xrange [0:1]` and friends: treat brackets as matched pairs.
setlocal matchpairs+=<:>

let b:undo_ftplugin = 'setlocal commentstring< comments< formatoptions<'
\ . ' iskeyword< matchpairs<'
\ . ' iskeyword<'

let &cpoptions = s:cpo_save
unlet s:cpo_save
53 changes: 34 additions & 19 deletions syntax/gnuplot.vim
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,27 @@ set cpoptions&vim
syn iskeyword @,48-57,_

" Comments ------------------------------------------------------------------
" A trailing backslash continues a comment onto the next line.
syn match gnuplotComment "#.*\%(\\\n.*\)*$" contains=gnuplotTodo,@Spell
" A trailing backslash continues a comment onto the next line, so the comment
" ends only at an end of line that is not preceded by one.
syn region gnuplotComment start="#" end="\\\@<!$" contains=gnuplotTodo,@Spell
syn keyword gnuplotTodo contained TODO FIXME XXX BUG NOTE HACK

" Strings -------------------------------------------------------------------
" Double quotes interpret backslash escapes; single quotes do not.
" Double quotes interpret backslash escapes; single quotes do not. gnuplot
" closes an unterminated string at the end of the line unless a trailing
" backslash continues it.
syn match gnuplotEscape contained "\\\%([\\abfnrtv"']\|[0-7]\{1,3}\|x\x\{1,2}\|u\x\{4}\|U\x\{8}\)"
syn match gnuplotFormat contained "%[-+ #0]*\d*\%(\.\d\+\)\?[hlLqjzt]*[diouxXeEfgGcsSpP%]"
syn region gnuplotString start=+"+ skip=+\\.+ end=+"+ contains=gnuplotEscape,gnuplotFormat,@Spell
syn region gnuplotString start=+'+ end=+'+ contains=gnuplotFormat,@Spell
syn region gnuplotString start=+"+ skip=+\\.+ end=+"\|\\\@<!$+ contains=gnuplotEscape,gnuplotFormat,@Spell
syn region gnuplotString start=+'+ end=+'\|\\\@<!$+ contains=gnuplotFormat,@Spell

" Datablocks ----------------------------------------------------------------
" `$data << EOD ... EOD` — the terminator is user-chosen, so match the whole
" block generically rather than pinning it to EOD.
" block generically rather than pinning it to EOD. The header may follow
" another command on the same line (`print 1; $d << EOD`), and `function $f <<
" EOF` blocks take the same form. gnuplot ends the block at the first line that
" begins with the terminator in column one, so ` EOD` is still data and
" `EODxyz` closes it.
" Column references in a using spec (`using ($1+$2)`). Digits, so this can
" never collide with the `$name` forms below.
syn match gnuplotColumnRef "\$\d\+"
Expand All @@ -49,7 +56,7 @@ syn match gnuplotColumnRef "\$\d\+"
" on `$name` would out-rank the region and stop it opening at all.
syn match gnuplotDatablockRef "\$\h\w*"
syn region gnuplotDatablock matchgroup=gnuplotDatablockName contains=NONE
\ start="^\s*\$\h\w*\s*<<\s*\z(\h\w*\)" end="^\s*\z1\s*$"
\ start="\$\h\w*\s*<<\s*\z(\h\w*\)" end="^\z1"
syn match gnuplotOperator "<<" containedin=gnuplotDatablockName

" Macros --------------------------------------------------------------------
Expand All @@ -58,9 +65,11 @@ syn match gnuplotMacro "@\h\w*"
" Numbers -------------------------------------------------------------------
" gnuplot folds a unit suffix into the number itself (10cm, 3.0in, 0.5pt). The
" full set is the grammar's typographic-unit alternation; `inch` has to precede
" `in` or the longer spelling never matches.
syn match gnuplotNumber "\<\d\+\%(\.\d*\)\?\%([eE][-+]\?\d\+\)\?\%(cm\|inch\|in\|mm\|pt\|pc\|bp\|dd\|cc\|pi\)\?\>"
syn match gnuplotNumber "\<\.\d\+\%([eE][-+]\?\d\+\)\?\%(cm\|inch\|in\|mm\|pt\|pc\|bp\|dd\|cc\|pi\)\?\>"
" `in` or the longer spelling never matches. A trailing dot (`1.`) belongs to
" the number, and gnuplot reads a dot before a digit as a number start (`.5`)
" even right after an identifier.
syn match gnuplotNumber "\<\d\+\%(\.\d*\)\?\%([eE][-+]\?\d\+\)\?\%(cm\|inch\|in\|mm\|pt\|pc\|bp\|dd\|cc\|pi\)\?\w\@!"
syn match gnuplotNumber "\.\d\+\%([eE][-+]\?\d\+\)\?\%(cm\|inch\|in\|mm\|pt\|pc\|bp\|dd\|cc\|pi\)\?\>"
syn match gnuplotNumber "\<0[xX]\x\+\>"

" Built-in variables --------------------------------------------------------
Expand All @@ -75,18 +84,19 @@ syn match gnuplotBuiltinVar "\<\w\+_\%(records\|headers\|outofrange\|invalid\|
syn keyword gnuplotBuiltinVar GNUTERM VoxelDistance GridDistance

" Functions -----------------------------------------------------------------
" Any identifier immediately followed by '(' reads as a call; the built-in list
" Any identifier followed by '(' reads as a call; the built-in list
" is defined afterwards so it wins on the same match position.
syn match gnuplotFunction "\<\h\w*\ze("
syn match gnuplotBuiltinFunction "\<\%(abs\|acosh\|acos\|airy\|arg\|asinh\|asin\|atan2\|atanh\|atan\|cbrt\|ceil\|conj\|cosh\|cos\|exp\|floor\|imag\|int\|log10\|log\|norm\|rand\|real\|round\|sgn\|sinh\|sin\|sqrt\|tanh\|tan\)\ze("
syn match gnuplotBuiltinFunction "\<\%(bes[ijy][01n]\|Ai\|Bi\|Bessel[HIJKY]1\?2\?\|Elliptic[EKP]i\?\|erfc\|erfi\|erf\|expint\|gamma\|ibeta\|igamma\|inverf\|invibeta\|invigamma\|invnorm\|LambertW\|lgamma\|lnGamma\|SynchrotronF\|uigamma\|voigt\|zeta\)\ze("
syn match gnuplotBuiltinFunction "\<\%(cdawson\|cerf\|faddeeva\|Fresnel[CS]\|VP\%(_fwhm\)\?\)\ze("
syn match gnuplotBuiltinFunction "\<\%(gprintf\|sprintf\|strlen\|strstrt\|substr\|split\|join\|trim\|word\|words\|system\)\ze("
syn match gnuplotBuiltinFunction "\<\%(column\|columnhead\%(er\)\?\|stringcolumn\|strcol\|exists\|valid\|value\|hsv2rgb\|palette\|rgbcolor\|voxel\|index\)\ze("
syn match gnuplotBuiltinFunction "\<\%(time\%(column\)\?\|strftime\|strptime\|tm_\w\+\|weekdate_\%(iso\|cdc\)\)\ze("
syn match gnuplotFunction "\<\h\w*\ze\s*("
syn match gnuplotBuiltinFunction "\<\%(abs\|acosh\|acos\|airy\|arg\|asinh\|asin\|atan2\|atanh\|atan\|cbrt\|ceil\|conj\|cosh\|cos\|exp\|floor\|imag\|int\|log10\|log\|norm\|rand\|real\|round\|sgn\|sinh\|sin\|sqrt\|tanh\|tan\)\ze\s*("
syn match gnuplotBuiltinFunction "\<\%(bes[ijy][01n]\|Ai\|Bi\|Bessel[HIJKY]1\?2\?\|Elliptic[EKP]i\?\|erfc\|erfi\|erf\|expint\|gamma\|ibeta\|igamma\|inverf\|invibeta\|invigamma\|invnorm\|LambertW\|lgamma\|lnGamma\|SynchrotronF\|uigamma\|voigt\|zeta\)\ze\s*("
syn match gnuplotBuiltinFunction "\<\%(cdawson\|cerf\|faddeeva\|Fresnel[CS]\|VP\%(_fwhm\)\?\)\ze\s*("
syn match gnuplotBuiltinFunction "\<\%(gprintf\|sprintf\|strlen\|strstrt\|substr\|split\|join\|trim\|word\|words\|system\)\ze\s*("
syn match gnuplotBuiltinFunction "\<\%(column\|columnhead\%(er\)\?\|stringcolumn\|strcol\|exists\|valid\|value\|hsv2rgb\|palette\|rgbcolor\|voxel\|index\)\ze\s*("
syn match gnuplotBuiltinFunction "\<\%(time\%(column\)\?\|strftime\|strptime\|tm_\w\+\|weekdate_\%(iso\|cdc\)\)\ze\s*("

" Operators -----------------------------------------------------------------
syn match gnuplotOperator "[-+*/%^!~?:.,]"
" A dot before a digit starts a number, not a concatenation.
syn match gnuplotOperator "[-+*/%^!~?:,]\|\.\d\@!"
syn match gnuplotOperator "\*\*"
syn match gnuplotOperator "[=!<>]="
syn match gnuplotOperator "[<>]"
Expand Down Expand Up @@ -364,6 +374,11 @@ syn keyword gnuplotConditional if else
syn keyword gnuplotRepeat do while for in
syn keyword gnuplotCommand array sum

" Synchronization -----------------------------------------------------------
" Datablocks have no length limit, so no fixed lookback can tell whether a line
" sits inside one. Parse from the top of the file instead.
syn sync fromstart

" Highlight links -----------------------------------------------------------
" The group names come from the grammar's capture taxonomy, where an alias
" names the clause family a word belongs to rather than the word itself: `cmd`
Expand Down
61 changes: 61 additions & 0 deletions tests/run.vim
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ filetype plugin on
syntax enable

let v:errors = []
" A script error (bad expression, unknown function) skips its assertion
" without touching v:errors, and nvim still exits 0, so it is caught below.
let v:errmsg = ''

" Raw syntax group at (line, col). Deliberately NOT synIDtrans: Vim collapses
" Conditional, Repeat and Keyword all into Statement, which would make most of
Expand All @@ -35,6 +38,22 @@ function! s:Word(text, word, expected) abort
\ printf('%s -> %s', a:text, a:word))
endfunction

" Assert the group under the first occurrence of {word} on line {lnum} of a
" multi-line buffer. Queried bottom-up, so the answer depends on the file
" syncing from the top rather than on what was parsed before.
function! s:Line(lines, lnum, word, expected) abort
enew!
setlocal filetype=gnuplot
call setline(1, a:lines)
let l:col = stridx(a:lines[a:lnum - 1], a:word) + 1
if l:col <= 0
call add(v:errors, printf('fixture error: %s not on line %d', a:word, a:lnum))
return
endif
call assert_equal(a:expected, s:Group(a:lnum, l:col),
\ printf('%s -> line %d %s', join(a:lines, ' | '), a:lnum, a:word))
endfunction

" --- commands ---------------------------------------------------------------
call s:Word('plot sin(x)', 'plot', 'gnuplotCommand')
call s:Word('p sin(x)', 'p', 'gnuplotCommand')
Expand Down Expand Up @@ -92,6 +111,17 @@ call s:Word('set size 1inch, 2mm', '1inch', 'gnuplotNumber')

" datablock contents are inert
call s:Word('$d << EOD', '$d', 'gnuplotDatablockName')
" A deep line reads as data even when nothing above it was parsed first.
call s:Line(['$d << EOD'] + repeat(['plot 1 # x'], 200) + ['EOD'], 150,
\ 'plot', 'gnuplotDatablock')
" gnuplot ends the block on the first line starting with the terminator in
" column one: ` EOD` is data, `EODxyz` closes the block.
call s:Line(['$d << EOD', ' EOD', 'plot 1'], 2, 'EOD', 'gnuplotDatablock')
call s:Line(['$d << EOD', ' EOD', 'plot 1'], 3, 'plot', 'gnuplotDatablock')
call s:Line(['$d << EOD', '1 2', 'EODxyz', 'plot 1'], 4, 'plot', 'gnuplotCommand')
" The header may follow another command on the same line.
call s:Line(['print 1; $d << EOD', '1 2', 'EOD'], 1, '$d', 'gnuplotDatablockName')
call s:Line(['print 1; $d << EOD', '1 2', 'EOD'], 2, '1', 'gnuplotDatablock')
call s:Word('set key noautotitle', 'noautotitle', 'gnuplotOption')

" --- tiers ------------------------------------------------------------------
Expand All @@ -105,7 +135,24 @@ call s:Word('set border front', 'front', 'gnuplotFlag')
call s:Word('plot "data.dat"', '"data.dat"', 'gnuplotString')
call s:Word("plot 'data.dat'", "'data.dat'", 'gnuplotString')
call s:Word('# a comment', '# a comment', 'gnuplotComment')
" A trailing backslash continues the comment; without one the next line is code.
call s:Line(['# comment\', 'print 42'], 2, 'print', 'gnuplotComment')
call s:Line(['print 1 # c\', 'print 42'], 2, '42', 'gnuplotComment')
call s:Line(['# comment', 'print 42'], 2, 'print', 'gnuplotCommand')
" gnuplot closes an unterminated string at the end of the line, unless a
" trailing backslash continues it.
call s:Line(['print "unfinished', 'print 42'], 2, 'print', 'gnuplotCommand')
call s:Line(["print 'unfinished", 'print 42'], 2, 'print', 'gnuplotCommand')
call s:Line(['print "abc\', 'def"', 'print 4'], 2, 'def', 'gnuplotString')
call s:Line(['print "abc\', 'def"', 'print 4'], 3, 'print', 'gnuplotCommand')
call s:Line(['print ''abc\', 'def''', 'print 4'], 2, 'def', 'gnuplotString')
call s:Word('print "a\"b" . x', 'x', '')
call s:Word('a = 42', '42', 'gnuplotNumber')
" Leading and trailing dots are part of the number, not the `.` operator.
call s:Word('a = .5', '.', 'gnuplotNumber')
call s:Word('a = 1.', '.', 'gnuplotNumber')
call s:Word('a = 1.e3', 'e', 'gnuplotNumber')
call s:Word('a = "x" . b', '.', 'gnuplotOperator')
call s:Word('set size 10cm, 5cm', '10cm', 'gnuplotNumber')
call s:Word('a = pi', 'pi', 'gnuplotConstant')
call s:Word('load @macro', '@macro', 'gnuplotMacro')
Expand All @@ -115,12 +162,22 @@ call s:Word('print STATS_mean_y', 'STATS_mean_y', 'gnuplotBuiltinVar')
" --- functions --------------------------------------------------------------
call s:Word('plot sin(x)', 'sin', 'gnuplotBuiltinFunction')
call s:Word('a = myfunc(2)', 'myfunc', 'gnuplotFunction')
" gnuplot allows whitespace before the argument list.
call s:Word('print sin (0)', 'sin', 'gnuplotBuiltinFunction')
call s:Word('a = myfunc (2)', 'myfunc', 'gnuplotFunction')

" --- control flow -----------------------------------------------------------
call s:Word('if (a > 1) { print "y" }', 'if', 'gnuplotConditional')
call s:Word('do for [i=1:10] { print i }', 'do', 'gnuplotRepeat')
call s:Word('do for [i=1:10] { print i }', 'for', 'gnuplotRepeat')

" --- ftplugin ---------------------------------------------------------------
" `<` and `>` are comparisons in gnuplot, never delimiters, so `%` must not
" pair them.
enew!
setlocal filetype=gnuplot
call assert_equal(-1, stridx(&l:matchpairs, '<:>'), 'matchpairs has no <:>')

" --- filetype detection -----------------------------------------------------
" `.gp` is not claimed: it belongs to PARI/GP in the stock runtimes.
for s:name in ['t.plt', 't.plot', 't.gnu', 't.gpi', 't.gnuplot',
Expand Down Expand Up @@ -150,6 +207,10 @@ function! s:Report(lines) abort
endif
endfunction

if !empty(v:errmsg)
call add(v:errors, 'script error: ' . v:errmsg)
endif

if empty(v:errors)
call s:Report(['ok - all syntax assertions passed'])
qall!
Expand Down
Loading