aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/._M.luadoc26
-rw-r--r--modules/lua/init.lua15
-rw-r--r--modules/textadept/run.lua91
3 files changed, 106 insertions, 26 deletions
diff --git a/core/._M.luadoc b/core/._M.luadoc
index 22006703..603ed299 100644
--- a/core/._M.luadoc
+++ b/core/._M.luadoc
@@ -25,7 +25,7 @@
-- Textadept's features, you should include at a minimum: run and/or compile
-- commands, an event handler for setting buffer properties like indentation,
-- and if possible, an autocompleter. Optional features are extra snippets,
--- commands, and context menu items.
+-- commands, context menu items, and a syntax checking command.
--
-- #### Compile and Run
--
@@ -122,10 +122,10 @@
-- ['s\n'] = M.try_to_autocomplete_end | end
-- } | }
--
--- When defining key bindings for other commands, you may make use of a `Ctrl+L`
--- (`⌘L` on Mac OSX | `M-L` in curses) keychain. Traditionally this prefix has
--- been reserved for use by language modules (although neither Textadept nor its
--- modules utilize it at the moment). Users may define this keychain for new or
+-- When defining key bindings for other commands, you may make use of a `Ctrl+L`
+-- (`⌘L` on Mac OSX | `M-L` in curses) keychain. Traditionally this prefix has
+-- been reserved for use by language modules (although neither Textadept nor its
+-- modules utilize it at the moment). Users may define this keychain for new or
-- existing modules and it will not conflict with any default key bindings.
--
-- keys.lua[not OSX and not CURSES and 'cl' or 'ml'] = {
@@ -142,4 +142,20 @@
-- title = 'Lua',
-- {'Autocomplete "end"', M.try_to_autocomplete_end}
-- }
+--
+-- #### Syntax Checking
+--
+-- Textadept has the ability to run a syntax checking tool on source files
+-- immediately after saving them, and automatically does this for a number of
+-- interpreted languages and markup languages. If your language is not supported
+-- (properly or at all), you will have to update the
+-- [`textadept.run.syntax_commands`]() and
+-- [`textadept.run.syntax_error_patterns`]() tables. For Lua, this looks like
+--
+-- textadept.run.syntax_commands.lua = 'luac -p "%f"'
+-- textadept.run.syntax_error_patterns.lua = ':(%d+): ([^\r\n]+)'
+--
+-- This feature really only makes sense for interpreted and markup languages.
+-- Compiled languages should make use of Textadept's "Compile and Run" and error
+-- recognition facilities.
module('_M')]]
diff --git a/modules/lua/init.lua b/modules/lua/init.lua
index f2ae7343..095aec79 100644
--- a/modules/lua/init.lua
+++ b/modules/lua/init.lua
@@ -115,21 +115,6 @@ function M.try_to_autocomplete_end()
return false
end
--- Show syntax errors as annotations.
-events.connect(events.FILE_AFTER_SAVE, function()
- if buffer:get_lexer() ~= 'lua' then return end
- buffer:annotation_clear_all()
- local text = buffer:get_text():gsub('^#![^\n]+', '') -- ignore shebang line
- local f, err = load(text)
- if f then return end
- local line, msg = err:match('^.-:(%d+):%s*(.+)$')
- if line then
- buffer.annotation_text[line - 1] = msg
- buffer.annotation_style[line - 1] = 8 -- error style number
- buffer:goto_line(line - 1)
- end
-end)
-
---
-- Container for Lua-specific key bindings.
-- @class table
diff --git a/modules/textadept/run.lua b/modules/textadept/run.lua
index 2ef9196f..e02fd344 100644
--- a/modules/textadept/run.lua
+++ b/modules/textadept/run.lua
@@ -4,15 +4,25 @@ local M = {}
--[[ This comment is for LuaDoc.
---
--- Compile and run source code files with Textadept.
+-- Compile, run, and check the syntax of source code files with Textadept.
-- [Language modules](#_M.Compile.and.Run) may tweak the `compile_commands`,
--- `run_commands`, and/or `error_patterns` tables for particular languages.
+-- `run_commands`, `error_patterns`, `syntax_commands`, and
+-- `syntax_error_patterns` tables for particular languages.
-- The user may tweak `build_commands` for particular projects.
-- @field RUN_IN_BACKGROUND (bool)
-- Run shell commands silently in the background.
-- This only applies when the message buffer is open, though it does not have
-- to be visible.
-- The default value is `false`.
+-- @field CHECK_SYNTAX (bool)
+-- Check the syntax of sources files upon saving them.
+-- This applies only to languages that have syntax-checking commands and error
+-- message patterns defined in the `syntax_commands` and
+-- `syntax_error_patterns` tables, respectively.
+-- The default value is `true`.
+-- @field GOTO_SYNTAX_ERRORS (bool)
+-- Immediately jump to recognized syntax errors after saving a source file.
+-- The default value is `true`.
-- @field MARK_WARNING (number)
-- The run or compile warning marker number.
-- @field MARK_ERROR (number)
@@ -50,6 +60,8 @@ local M = {}
module('textadept.run')]]
M.RUN_IN_BACKGROUND = false
+M.CHECK_SYNTAX = true
+M.GOTO_SYNTAX_ERRORS = true
M.MARK_WARNING = _SCINTILLA.next_marker_number()
M.MARK_ERROR = _SCINTILLA.next_marker_number()
@@ -172,8 +184,8 @@ end
--
-- + `%f`: The file's name, including its extension.
-- + `%e`: The file's name, excluding its extension.
--- + `%d`: The current file's directory path.
--- + `%p`: The current file's full path.
+-- + `%d`: The file's directory path.
+-- + `%p`: The file's full path.
-- @class table
-- @name compile_commands
M.compile_commands = {actionscript='mxmlc "%f"',ada='gnatmake "%f"',ansi_c='gcc -o "%e" "%f"',antlr='antlr4 "%f"',g='antlr3 "%f"',applescript='osacompile "%f" -o "%e.scpt"',asm='nasm "%f" && ld "%e.o" -o "%e"',boo='booc "%f"',caml='ocamlc -o "%e" "%f"',csharp=WIN32 and 'csc "%f"' or 'mcs "%f"',cpp='g++ -o "%e" "%f"',coffeescript='coffee -c "%f"',context='context --nonstopmode "%f"',cuda=WIN32 and 'nvcc -o "%e.exe" "%f"' or 'nvcc -o "%e" "%f"',dmd='dmd "%f"',dot='dot -Tps "%f" -o "%e.ps"',eiffel='se c "%f"',elixir='elixirc "%f"',erlang='erl -compile "%e"',fsharp=WIN32 and 'fsc.exe "%f"' or 'mono fsc.exe "%f"',fortran='gfortran -o "%e" "%f"',gap='gac -o "%e" "%f"',go='go build "%f"',groovy='groovyc "%f"',haskell=WIN32 and 'ghc -o "%e.exe" "%f"' or 'ghc -o "%e" "%f"',inform=function() return 'inform -c "'..buffer.filename:match('^(.+%.inform[/\\])Source')..'"' end,java='javac "%f"',ltx='pdflatex -file-line-error -halt-on-error "%f"',less='lessc "%f" "%e.css"',lilypond='lilypond "%f"',lisp='clisp -c "%f"',litcoffee='coffee -c "%f"',lua='luac -o "%e.luac" "%f"',markdown='markdown "%f" > "%e.html"',nemerle='ncc "%f" -out:"%e.exe"',nim='nim c "%f"',nsis='MakeNSIS "%f"',objective_c='gcc -o "%e" "%f"',pascal='fpc "%f"',perl='perl -c "%f"',php='php -l "%f"',prolog='gplc --no-top-level "%f"',python='python -m py_compile "%f"',ruby='ruby -c "%f"',rust='rustc "%f"',sass='sass "%f" "%e.css"',scala='scalac "%f"',tex='pdflatex -file-line-error -halt-on-error "%f"',vala='valac "%f"',vb=WIN32 and 'vbc "%f"' or 'vbnc "%f"',}
@@ -195,8 +207,8 @@ events.connect(events.COMPILE_OUTPUT, print_output)
--
-- + `%f`: The file's name, including its extension.
-- + `%e`: The file's name, excluding its extension.
--- + `%d`: The current file's directory path.
--- + `%p`: The full path of the current file.
+-- + `%d`: The file's directory path.
+-- + `%p`: The file's full path.
-- @class table
-- @name run_commands
M.run_commands = {actionscript=WIN32 and 'start "" "%e.swf"' or OSX and 'open "file://%e.swf"' or 'xdg-open "%e.swf"',ada=WIN32 and '"%e"' or './"%e"',ansi_c=WIN32 and '"%e"' or './"%e"',applescript='osascript "%f"',asm='./"%e"',awk='awk -f "%f"',batch='"%f"',boo='booi "%f"',caml='ocamlrun "%e"',csharp=WIN32 and '"%e"' or 'mono "%e.exe"',cpp=WIN32 and '"%e"' or './"%e"',chuck='chuck "%f"',cmake='cmake -P "%f"',coffeescript='coffee "%f"',context=WIN32 and 'start "" "%e.pdf"' or OSX and 'open "%e.pdf"' or 'xdg-open "%e.pdf"',cuda=WIN32 and '"%e"' or './"%e"',dmd=WIN32 and '"%e"' or './"%e"',eiffel="./a.out",elixir='elixir "%f"',fsharp=WIN32 and '"%e"' or 'mono "%e.exe"',forth='gforth "%f" -e bye',fortran=WIN32 and '"%e"' or './"%e"',gnuplot='gnuplot "%f"',go='go run "%f"',groovy='groovy "%f"',haskell=WIN32 and '"%e"' or './"%e"',html=WIN32 and 'start "" "%f"' or OSX and 'open "file://%f"' or 'xdg-open "%f"',idl='idl -batch "%f"',Io='io "%f"',java='java "%e"',javascript='node "%f"',ltx=WIN32 and 'start "" "%e.pdf"' or OSX and 'open "%e.pdf"' or 'xdg-open "%e.pdf"',less='lessc --no-color "%f"',lilypond=WIN32 and 'start "" "%e.pdf"' or OSX and 'open "%e.pdf"' or 'xdg-open "%e.pdf"',lisp='clisp "%f"',litcoffee='coffee "%f"',lua='lua -e "io.stdout:setvbuf(\'no\')" "%f"',makefile=WIN32 and 'nmake -f "%f"' or 'make -f "%f"',markdown='markdown "%f"',nemerle=WIN32 and '"%e"' or 'mono "%e.exe"',nim='nim c -r "%f"',objective_c=WIN32 and '"%e"' or './"%e"',pascal=WIN32 and '"%e"' or './"%e"',perl='perl "%f"',php='php "%f"',pike='pike "%f"',pkgbuild='makepkg -p "%f"',prolog=WIN32 and '"%e"' or './"%e"',python='python -u "%f"',rstats=WIN32 and 'Rterm -f "%f"' or 'R -f "%f"',rebol='REBOL "%f"',rexx=WIN32 and 'rexx "%e"' or 'regina "%e"',ruby='ruby "%f"',rust=WIN32 and '"%e"' or './"%e"',sass='sass "%f"',scala='scala "%e"',bash='bash "%f"',csh='tcsh "%f"',sh='sh "%f"',zsh='zsh "%f"',smalltalk='gst "%f"',tcl='tclsh "%f"',tex=WIN32 and 'start "" "%e.pdf"' or OSX and 'open "%e.pdf"' or 'xdg-open "%e.pdf"',vala=WIN32 and '"%e"' or './"%e"',vb=WIN32 and '"%e"' or 'mono "%e.exe"',}
@@ -326,4 +338,71 @@ events.connect(events.DOUBLE_CLICK, function(_, line)
if is_msg_buf(buffer) and M.cwd then M.goto_error(line) end
end)
+---
+-- Map of file extensions or lexer names to their associated syntax checker
+-- command line strings or functions that return such strings.
+-- `%f` in command line strings represents the file to check the syntax of.
+-- Upon saving a source file, this table is consulted for potentially running a
+-- syntax checking utility on that file. This usually only makes sense for
+-- interpreted languages and markup languages.
+-- @class table
+-- @name syntax_commands
+M.syntax_commands = {awk='gawk --source "BEGIN{exit(0)} END{exit(0)}" --file "%f"',bash = function() return (buffer:get_line(0):match('^#!.+/([^/%s]+)') or 'bash')..' -n "%f"' end,coffeescript='coffee -cp "%f"',css='csslint --format=compact --quiet "%f"',fish='fish -n "%f"',go='gofmt -l "%f"',html='tidy -e -q -utf8 "%f"',javascript='jshint "%f"',less='lessc --lint --no-color "%f"',litcoffee='coffee -cp "%f"',lua='luac -p "%f"',perl='perl -c -X "%f"',php='php -l "%f"',python=[[python -c 'compile(open("%f").read(),"%f","exec",0,1)']],ruby='ruby -c "%f"',sass='sass -c -q "%f"',xml='xmllint "%f"',}
+
+---
+-- Map of file extensions or lexer names to patterns that match their respective
+-- syntax-checkers' error messages or functions that return such patterns.
+-- Patterns contain line number, optional column number, and error message
+-- captures.
+-- When adding to this map, use `(%d+)` to match line and column numbers.
+-- `(%s*)` may also be used to match column numbers for visual error messages.
+-- @class table
+-- @name syntax_error_patterns
+M.syntax_error_patterns = {awk=':(%d+): (%s*)^ ([^\r\n]+)',bash='[:%s](%d+): ([^\r\n]+)',coffeescript='In [^,]+, (.-) on line (%d+):?([^\r\n]*)',css='line (%d+), col (%d+), ([^\r\n]+)',fish='fish: ([^\r\n]+).-line (%d+).:',go=':(%d+):(%d+): ([^\r\n]+)',html='line (%d+) column (%d+) %- Error: ([^\r\n]+)',javascript='line (%d+), col (%d+), ([^\r\n]+)',less='^(.-) in .- on line (%d+), column (%d+):',litcoffee='In [^,]+, (.-) on line (%d+):?([^\r\n]*)',lua=':(%d+): ([^\r\n]+)',perl='^(.-) at .- line (%d+)',php='^(.-) in .- on line (%d+)',python='", line (%d+)[\r\n]+.-(%w+: [^\r\n]+)',ruby=':(%d+): ([^\r\n]+).-[\r\n]+(%s*)^',sass='^([^\r\n]+).-on line (%d+)',xml=':(%d+): ([^\r\n]+).-[\r\n]+(%s*)^',}
+
+-- Check syntax upon saving a file.
+events.connect(events.FILE_AFTER_SAVE, function(filename)
+ if not M.CHECK_SYNTAX then return end
+ -- Determine the syntax checker command.
+ local ext, lexer = buffer.filename:match('[^.]+$'), buffer:get_lexer()
+ local command = M.syntax_commands[ext] or M.syntax_commands[lexer]
+ local patt = M.syntax_error_patterns[ext] or M.syntax_error_patterns[lexer]
+ if type(command) == 'function' then command = command() end
+ if type(patt) == 'function' then patt = patt() end
+ if not command or not patt then return end
+ -- Run the syntax checker command and look for errors.
+ buffer:annotation_clear_all()
+ local p = io.popen(command:gsub('%%f', filename)..' 2>&1')
+ local out = p:read('*a')
+ p:close()
+ local captures = {message = '', out:match(patt)}
+ if #captures == 0 then return end
+ -- Parse out the line, column, and error message.
+ for detail in patt:gmatch('[^%%](%b())') do
+ if detail == '(%d+)' then
+ local source = not captures.line and 'line' or 'column'
+ captures[source] = tonumber(table.remove(captures, 1)) - 1
+ elseif detail == '(%s*)' then
+ captures.column = #table.remove(captures, 1)
+ else
+ captures.message = captures.message..table.remove(captures, 1)
+ end
+ end
+ if not captures.line or not captures.message then return end
+ -- Display the annotation and either jump to, or note the position.
+ buffer.annotation_text[captures.line] = captures.message
+ buffer.annotation_style[captures.line] = 8 -- error style number
+ local top_line = buffer:doc_line_from_visible(buffer.first_visible_line)
+ local bottom_line = buffer:doc_line_from_visible(buffer.first_visible_line +
+ buffer.lines_on_screen) - 1
+ if M.GOTO_SYNTAX_ERRORS then
+ buffer:goto_pos(buffer:find_column(captures.line, captures.column or 0))
+ elseif captures.line < top_line or captures.line > bottom_line then
+ local line = buffer:line_from_position(buffer.current_pos)
+ buffer.annotation_text[line] = 'Line '..(captures.line + 1)..'\n'..
+ captures.message
+ buffer.annotation_style[line] = 8 -- error style number
+ end
+end)
+
return M