1-- Copyright 2007-2021 Mitchell. See LICENSE. 2 3local M = {} 4 5--[[ This comment is for LuaDoc. 6--- 7-- Compile and run source code files with Textadept. 8-- [Language modules](#compile-and-run) may tweak the `compile_commands`, 9-- `run_commands`, and `error_patterns` tables for particular languages. 10-- The user may tweak `build_commands` and `test_commands` for particular 11-- projects. 12-- @field run_in_background (bool) 13-- Run shell commands silently in the background. 14-- This only applies when the message buffer is open, though it does not have 15-- to be visible. 16-- The default value is `false`. 17-- @field MARK_WARNING (number) 18-- The run or compile warning marker number. 19-- @field MARK_ERROR (number) 20-- The run or compile error marker number. 21-- @field _G.events.COMPILE_OUTPUT (string) 22-- Emitted when executing a language's compile shell command. 23-- By default, compiler output is printed to the message buffer. In order to 24-- override this behavior, connect to the event with an index of `1` and 25-- return `true`. 26-- Arguments: 27-- 28-- * `output`: A line of string output from the command. 29-- * `ext_or_lexer`: The file extension or lexer name associated with the 30-- executed compile command. 31-- @field _G.events.RUN_OUTPUT (string) 32-- Emitted when executing a language's run shell command. 33-- By default, output is printed to the message buffer. In order to override 34-- this behavior, connect to the event with an index of `1` and return `true`. 35-- Arguments: 36-- 37-- * `output`: A line of string output from the command. 38-- * `ext_or_lexer`: The file extension or lexer name associated with the 39-- executed run command. 40-- @field _G.events.BUILD_OUTPUT (string) 41-- Emitted when executing a project's build shell command. 42-- By default, output is printed to the message buffer. In order to override 43-- this behavior, connect to the event with an index of `1` and return `true`. 44-- Arguments: 45-- 46-- * `output`: A line of string output from the command. 47-- @field _G.events.TEST_OUTPUT (string) 48-- Emitted when executing a project's shell command for running tests. 49-- By default, output is printed to the message buffer. In order to override 50-- this behavior, connect to the event with an index of `1` and return `true`. 51-- Arguments: 52-- 53-- * `output`: A line of string output from the command. 54module('textadept.run')]] 55 56M.run_in_background = false 57 58M.MARK_WARNING = _SCINTILLA.next_marker_number() 59M.MARK_ERROR = _SCINTILLA.next_marker_number() 60 61-- Events. 62local run_events = { 63 'compile_output', 'run_output', 'build_output', 'test_output' 64} 65for _, v in ipairs(run_events) do events[v:upper()] = v end 66 67-- Keep track of: the last process spawned in order to kill it if requested; the 68-- cwd of that process in order to jump to relative file paths in recognized 69-- warning or error messages; and the view the process was spawned from in order 70-- to jump to messages (which are displayed in a split view) in the original 71-- view. 72local proc, cwd, preferred_view 73 74-- Scans the given message for a warning or error message and, if one is found, 75-- returns table of the warning/error's details. 76-- @param message The message to parse for warnings or errors. The message 77-- is assumed to be encoded in _CHARSET. 78-- @param ext_or_lexer Optional file extension or lexer name associated with the 79-- shell command that produced the warning/error. 80-- @return error details table with 'filename', 'line', 'column', and 'message' 81-- fields along with a 'warning' flag. 82-- @see error_patterns 83local function scan_for_error(message, ext_or_lexer) 84 for key, patterns in pairs(M.error_patterns) do 85 if ext_or_lexer and key ~= ext_or_lexer then goto continue end 86 for _, patt in ipairs(patterns) do 87 if not message:find(patt) then goto continue end 88 -- Extract details from the warning or error. 89 local detail, i = {message:match(patt)}, 1 90 for capture in patt:gmatch('[^%%](%b())') do 91 if capture == '(.-)' then 92 detail.filename = detail[i] 93 elseif capture == '(%d+)' then 94 local line_or_column = not detail.line and 'line' or 'column' 95 detail[line_or_column] = tonumber(detail[i]) 96 else 97 detail.message = detail[i] 98 end 99 i = i + 1 100 end 101 detail.warning = 102 message:lower():find('warning') and not message:lower():find('error') 103 -- Compile and run commands specify the file extension or lexer name used 104 -- to determine the command, so the error patterns used are guaranteed to 105 -- be correct. Build and test commands have no such context and instead 106 -- iterate through all possible error patterns. Only consider the 107 -- error/warning valid if the extracted filename's extension or lexer name 108 -- matches the error pattern's extension or lexer name. 109 if ext_or_lexer then return detail end 110 local ext = detail.filename:match('[^/\\.]+$') 111 local lexer_name = textadept.file_types.extensions[ext] 112 if key == ext or key == lexer_name then return detail end 113 ::continue:: 114 end 115 ::continue:: 116 end 117 return nil 118end 119 120-- Prints an output line from a compile, run, build, or test shell command. 121-- Assume output is UTF-8 unless there's a recognized warning or error message. 122-- In that case assume it is encoded in _CHARSET and mark it. 123-- All stdout and stderr from the command is printed silently. 124-- @param line The output line to print. 125-- @param ext_or_lexer Optional file extension or lexer name associated with the 126-- executed command. This is used for better error detection in compile and 127-- run commands. 128local function print_line(line, ext_or_lexer) 129 local error = scan_for_error(line, ext_or_lexer) 130 ui.silent_print = M.run_in_background or ext_or_lexer or 131 not line:find('^> ') or line:find('^> exit') 132 ui.print(not error and line or line:iconv('UTF-8', _CHARSET)) 133 ui.silent_print = false 134 if error then 135 -- Current position is one line below the error due to ui.print()'s '\n'. 136 buffer:marker_add( 137 buffer.line_count - 1, error.warning and M.MARK_WARNING or M.MARK_ERROR) 138 end 139end 140 141local output_buffer 142-- Prints the output from a compile, run, build, or test shell command as a 143-- series of lines, performing buffering as needed. 144-- @param output The output to print, or `nil` to flush any buffered output. 145-- @param ext_or_lexer Optional file extension or lexer name associated with the 146-- executed command. This is used for better error detection in compile and 147-- run commands. 148local function print_output(output, ext_or_lexer) 149 if output then 150 if output_buffer then output = output_buffer .. output end 151 local remainder = 1 152 for line, e in output:gmatch('([^\r\n]*)\r?\n()') do 153 print_line(line, ext_or_lexer) 154 remainder = e 155 end 156 output_buffer = remainder <= #output and output:sub(remainder) 157 elseif output_buffer then 158 print_line(output_buffer, ext_or_lexer) 159 output_buffer = nil 160 end 161end 162 163-- Runs command *command* in working directory *dir*, emitting events of type 164-- *event* with any output received. 165-- @param command String command to run, or a function returning such a string 166-- and optional working directory and environment table. A returned working 167-- directory overrides *dir*. 168-- @param dir String working directory to run *command* in. 169-- @param event String event name to emit command output with. 170-- @param macros Optional table of '%[char]' macros to expand within *command*. 171-- @param ext_or_lexer Optional file extension or lexer name associated with the 172-- executed command. This is used for better error detection in compile and 173-- run commands. 174local function run_command(command, dir, event, macros, ext_or_lexer) 175 local working_dir, env 176 if type(command) == 'function' then command, working_dir, env = command() end 177 if not command then return end 178 if macros then command = command:gsub('%%%a', macros) end 179 preferred_view = view 180 local function emit(output) events.emit(event, output, ext_or_lexer) end 181 cwd = (working_dir or dir):gsub('[/\\]$', '') 182 events.emit(event, string.format('> cd %s\n', cwd)) 183 events.emit(event, string.format('> %s\n', command:iconv('UTF-8', _CHARSET))) 184 local args = {command, cwd, emit, emit, function(status) 185 emit() -- flush 186 events.emit(event, string.format('> exit status: %d\n', status)) 187 end} 188 if env then table.insert(args, 3, env) end 189 proc = assert(os.spawn(table.unpack(args))) 190end 191 192-- Compiles or runs file *filename* based on a shell command in *commands*. 193-- @param filename The file to run. 194-- @param commands Either `compile_commands` or `run_commands`. 195local function compile_or_run(filename, commands) 196 if filename == buffer.filename then 197 buffer:annotation_clear_all() 198 if buffer.modify then buffer:save() end 199 end 200 local ext = filename:match('[^/\\.]+$') 201 local lang = filename == buffer.filename and buffer:get_lexer() or 202 textadept.file_types.extensions[ext] 203 local command = commands[filename] or commands[ext] or commands[lang] 204 local dirname, basename = '', filename 205 if filename:find('[/\\]') then 206 dirname, basename = filename:match('^(.+)[/\\]([^/\\]+)$') 207 end 208 local event = commands == M.compile_commands and events.COMPILE_OUTPUT or 209 events.RUN_OUTPUT 210 local macros = { 211 ['%p'] = filename, ['%d'] = dirname, ['%f'] = basename, 212 ['%e'] = basename:match('^(.+)%.') -- no extension 213 } 214 run_command(command, dirname, event, macros, commands[ext] and ext or lang) 215end 216 217--- 218-- Map of filenames, file extensions, and lexer names to their associated 219-- "compile" shell command line strings or functions that return such strings. 220-- Command line strings may have the following macros: 221-- 222-- + `%f`: The file's name, including its extension. 223-- + `%e`: The file's name, excluding its extension. 224-- + `%d`: The file's directory path. 225-- + `%p`: The file's full path. 226-- 227-- Functions may also return a working directory and process environment table 228-- to operate in. By default, the working directory is the current file's parent 229-- directory and the environment is Textadept's environment. 230-- @class table 231-- @name compile_commands 232M.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"',coffeescript='coffee -c "%f"',context='context --nonstopmode "%f"',cpp='g++ -o "%e" "%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"',faust='faust -o "%e.cpp" "%f"',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 --no-color "%f" "%e.css"',lilypond='lilypond "%f"',lisp='clisp -c "%f"',litcoffee='coffee -c "%f"',lua='luac -o "%e.luac" "%f"',moon='moonc "%f"',markdown='markdown "%f" > "%e.html"',myr='mbld -b "%e" "%f"',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"',pony='ponyc "%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"',sml='mlton "%f"',tex='pdflatex -file-line-error -halt-on-error "%f"',vala='valac "%f"',vb=WIN32 and 'vbc "%f"' or 'vbnc "%f"',zig='zig build-exe "%f"'} 233 234--- 235-- Compiles file *filename* or the current file using an appropriate shell 236-- command from the `compile_commands` table. 237-- The shell command is determined from the file's filename, extension, or 238-- language in that order. 239-- Emits `COMPILE_OUTPUT` events. 240-- @param filename Optional path to the file to compile. The default value is 241-- the current file's filename. 242-- @see compile_commands 243-- @see _G.events 244-- @name compile 245function M.compile(filename) 246 if assert_type(filename, 'string/nil', 1) or buffer.filename then 247 compile_or_run(filename or buffer.filename, M.compile_commands) 248 end 249end 250events.connect(events.COMPILE_OUTPUT, print_output) 251 252--- 253-- Map of filenames, file extensions, and lexer names to their associated "run" 254-- shell command line strings or functions that return strings. 255-- Command line strings may have the following macros: 256-- 257-- + `%f`: The file's name, including its extension. 258-- + `%e`: The file's name, excluding its extension. 259-- + `%d`: The file's directory path. 260-- + `%p`: The file's full path. 261-- 262-- Functions may also return a working directory and process environment table 263-- to operate in. By default, the working directory is the current file's parent 264-- directory and the environment is Textadept's environment. 265-- @class table 266-- @name run_commands 267M.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"',chuck='chuck "%f"',clojure='clj -M "%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"',cpp=WIN32 and '"%e"' or './"%e"',crystal='crystal "%f"',cuda=WIN32 and '"%e"' or './"%e"',dart='dart "%f"',dmd=WIN32 and '"%e"' or './"%e"',eiffel="./a.out",elixir='elixir "%f"',fsharp=WIN32 and '"%e"' or 'mono "%e.exe"',fantom='fan "%f"',fennel='fennel "%f"',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"',icon='icont "%e" -x',idl='idl -batch "%f"',Io='io "%f"',java='java "%e"',javascript='node "%f"',jq='jq -f "%f"',julia='julia "%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"',moon='moon "%f"',myr=WIN32 and '"%e"' or './"%e"',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"',pony=WIN32 and '"%e"' or './"%e"',prolog=WIN32 and '"%e"' or './"%e"',pure='pure "%f"',python=function() return buffer:get_line(1):find('^#!.-python3') and 'python3 -u "%f"' or 'python -u "%f"' end,rstats=WIN32 and 'Rterm -f "%f"' or 'R -f "%f"',rebol='REBOL "%f"',rexx=WIN32 and 'rexx "%f"' or 'regina "%f"',ruby='ruby "%f"',rust=WIN32 and '"%e"' or './"%e"',sass='sass "%f"',scala='scala "%e"',bash='bash "%f"',csh='tcsh "%f"',ksh='ksh "%f"',mksh='mksh "%f"',sh='sh "%f"',zsh='zsh "%f"',rc='rc "%f"',smalltalk='gst "%f"',sml=WIN32 and '"%e"' or './"%e"',snobol4='snobol4 -b "%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"',xs='xs "%f"',zig=WIN32 and '"%e"' or './"%e"'} 268 269--- 270-- Runs file *filename* or the current file using an appropriate shell command 271-- from the `run_commands` table. 272-- The shell command is determined from the file's filename, extension, or 273-- language in that order. 274-- Emits `RUN_OUTPUT` events. 275-- @param filename Optional path to the file to run. The default value is the 276-- current file's filename. 277-- @see run_commands 278-- @see _G.events 279-- @name run 280function M.run(filename) 281 if assert_type(filename, 'string/nil', 1) or buffer.filename then 282 compile_or_run(filename or buffer.filename, M.run_commands) 283 end 284end 285events.connect(events.RUN_OUTPUT, print_output) 286 287--- 288-- Appends the command line argument strings *run* and *compile* to their 289-- respective run and compile commands for file *filename* or the current file. 290-- If either is `nil`, prompts the user for missing the arguments. Each filename 291-- has its own set of compile and run arguments. 292-- @param filename Optional path to the file to set run/compile arguments for. 293-- @param run Optional string run arguments to set. If `nil`, the user is 294-- prompted for them. Pass the empty string for no run arguments. 295-- @param compile Optional string compile arguments to set. If `nil`, the user 296-- is prompted for them. Pass the empty string for no compile arguments. 297-- @see run_commands 298-- @see compile_commands 299-- @name set_arguments 300function M.set_arguments(filename, run, compile) 301 if not assert_type(filename, 'string/nil', 1) then 302 filename = buffer.filename 303 if not filename then return end 304 end 305 assert_type(run, 'string/nil', 2) 306 assert_type(compile, 'string/nil', 3) 307 local base_commands, utf8_args = {}, {} 308 for i, commands in ipairs{M.run_commands, M.compile_commands} do 309 -- Compare the base run/compile command with the one for the current 310 -- file. The difference is any additional arguments set previously. 311 base_commands[i] = commands[filename:match('[^.]+$')] or 312 commands[buffer:get_lexer()] or '' 313 local current_command = commands[filename] or '' 314 local args = (i == 1 and run or compile) or 315 current_command:sub(#base_commands[i] + 2) 316 utf8_args[i] = args:iconv('UTF-8', _CHARSET) 317 end 318 if not run or not compile then 319 local button 320 button, utf8_args = ui.dialogs.inputbox{ 321 title = _L['Set Arguments...']:gsub('_', ''), informative_text = { 322 _L['Command line arguments'], _L['For Run:'], _L['For Compile:'] 323 }, text = utf8_args, width = not CURSES and 400 or nil 324 } 325 if button ~= 1 then return end 326 end 327 for i, commands in ipairs{M.run_commands, M.compile_commands} do 328 -- Add the additional arguments to the base run/compile command and set 329 -- the new command to be the one used for the current file. 330 commands[filename] = string.format('%s %s', base_commands[i], 331 utf8_args[i]:iconv(_CHARSET, 'UTF-8')) 332 end 333end 334 335--- 336-- Map of project root paths and "makefiles" to their associated "build" shell 337-- command line strings or functions that return such strings. 338-- Functions may also return a working directory and process environment table 339-- to operate in. By default, the working directory is the project's root 340-- directory and the environment is Textadept's environment. 341-- @class table 342-- @name build_commands 343M.build_commands = {--[[Ant]]['build.xml']='ant',--[[Dockerfile]]Dockerfile='docker build .',--[[Make]]Makefile='make',GNUmakefile='make',makefile='make',--[[Meson]]['meson.build']='meson compile',--[[Maven]]['pom.xml']='mvn',--[[Ruby]]Rakefile='rake'} 344 345--- 346-- Builds the project whose root path is *root_directory* or the current project 347-- using the shell command from the `build_commands` table. 348-- If a "makefile" type of build file is found, prompts the user for the full 349-- build command. 350-- The current project is determined by either the buffer's filename or the 351-- current working directory. 352-- Emits `BUILD_OUTPUT` events. 353-- @param root_directory The path to the project to build. The default value is 354-- the current project. 355-- @see build_commands 356-- @see _G.events 357-- @name build 358function M.build(root_directory) 359 if not assert_type(root_directory, 'string/nil', 1) then 360 root_directory = io.get_project_root() 361 if not root_directory then return end 362 end 363 for i = 1, #_BUFFERS do _BUFFERS[i]:annotation_clear_all() end 364 local command = M.build_commands[root_directory] 365 if not command then 366 for build_file, build_command in pairs(M.build_commands) do 367 if lfs.attributes(string.format('%s/%s', root_directory, build_file)) then 368 local button, utf8_command = ui.dialogs.inputbox{ 369 title = _L['Command'], informative_text = root_directory, 370 text = build_command, button1 = _L['OK'], button2 = _L['Cancel'] 371 } 372 if button == 1 then command = utf8_command:iconv(_CHARSET, 'UTF-8') end 373 break 374 end 375 end 376 end 377 run_command(command, root_directory, events.BUILD_OUTPUT) 378end 379events.connect(events.BUILD_OUTPUT, print_output) 380 381--- 382-- Map of project root paths to their associated "test" shell command line 383-- strings or functions that return such strings. 384-- Functions may also return a working directory and process environment table 385-- to operate in. By default, the working directory is the project's root 386-- directory and the environment is Textadept's environment. 387-- @class table 388-- @name test_commands 389M.test_commands = {} 390 391--- 392-- Runs tests for the project whose root path is *root_directory* or the current 393-- project using the shell command from the `test_commands` table. 394-- The current project is determined by either the buffer's filename or the 395-- current working directory. 396-- Emits `TEST_OUTPUT` events. 397-- @param root_directory The path to the project to run tests for. The default 398-- value is the current project. 399-- @see test_commands 400-- @see _G.events 401-- @name test 402function M.test(root_directory) 403 if not assert_type(root_directory, 'string/nil', 1) then 404 root_directory = io.get_project_root() 405 if not root_directory then return end 406 end 407 for i = 1, #_BUFFERS do _BUFFERS[i]:annotation_clear_all() end 408 run_command( 409 M.test_commands[root_directory], root_directory, events.TEST_OUTPUT) 410end 411events.connect(events.TEST_OUTPUT, print_output) 412 413--- 414-- Stops the currently running process, if any. 415-- @name stop 416function M.stop() if proc then proc:kill() end end 417 418-- Returns whether or not the given buffer is the message buffer. 419local function is_msg_buf(buf) return buf._type == _L['[Message Buffer]'] end 420 421-- Send line as input to process stdin on return. 422events.connect(events.CHAR_ADDED, function(code) 423 if code == string.byte('\n') and proc and proc:status() == 'running' and 424 is_msg_buf(buffer) then 425 local line_num = buffer:line_from_position(buffer.current_pos) - 1 426 proc:write(buffer:get_line(line_num)) 427 end 428end) 429 430--- 431-- Map of file extensions and lexer names to their associated lists of string 432-- patterns that match warning and error messages emitted by compile and run 433-- commands for those file extensions and lexers. 434-- Patterns match single lines and contain captures for a filename, line number, 435-- column number (optional), and warning or error message (optional). 436-- Double-clicking a warning or error message takes the user to the source of 437-- that warning/error. 438-- Note: `(.-)` captures in patterns are interpreted as filenames; `(%d+)` 439-- captures are interpreted as line numbers first, and then column numbers; and 440-- any other capture is treated as warning/error message text. 441-- @class table 442-- @name error_patterns 443M.error_patterns = {actionscript={'^(.-)%((%d+)%): col: (%d+) (.+)$'},ada={'^(.-):(%d+):(%d+):%s*(.*)$','^[^:]+: (.-):(%d+) (.+)$'},ansi_c={'^(.-):(%d+):(%d+): (.+)$'},antlr={'^error%(%d+%): (.-):(%d+):(%d+): (.+)$','^warning%(%d+%): (.-):(%d+):(%d+): (.+)$'},--[[ANTLR]]g={'^error%(%d+%): (.-):(%d+):(%d+): (.+)$','^warning%(%d+%): (.-):(%d+):(%d+): (.+)$'},asm={'^(.-):(%d+): (.+)$'},awk={'^awk: (.-):(%d+): (.+)$'},boo={'^(.-)%((%d+),(%d+)%): (.+)$'},caml={'^%s*File "(.-)", line (%d+), characters (%d+)'},chuck={'^(.-)line%((%d+)%)%.char%((%d+)%): (.+)$'},clojure={' error .- at .-%((.-):(%d+)'},cmake={'^CMake Error at (.-):(%d+)','^(.-):(%d+):$'},coffeescript={'^(.-):(%d+):(%d+): (.+)$'},context={'error on line (%d+) in file (.-): (.+)$'},cpp={'^(.-):(%d+):(%d+): (.+)$'},csharp={'^(.-)%((%d+),(%d+)%): (.+)$'},cuda={'^(.-)%((%d+)%): (error.+)$'},dart={"^'(.-)': error: line (%d+) pos (%d+): (.+)$",'%(file://(.-):(%d+):(%d+)%)'},dmd={'^(.-)%((%d+)%): (Error.+)$'},dot={'^Warning: (.-): (.+) in line (%d+)'},eiffel={'^Line (%d+) columns? .- in .- %((.-)%):$','^line (%d+) column (%d+) file (.-)$'},elixir={'^(.-):(%d+): (.+)$','Error%) (.-):(%d+): (.+)$'},erlang={'^(.-):(%d+): (.+)$'},fantom={'^(.-)%((%d+),(%d+)%): (.+)$'},faust={'^(.-):(%d+):(.+)$'},fennel={'^%S+ error in (.-):(%d+)'},forth={'^(.-):(%d+): (.+)$'},fortran={'^(.-):(%d+)%D+(%d+):%s*(.*)$'},fsharp={'^(.-)%((%d+),(%d+)%): (.+)$'},gap={'^(.+) in (.-) line (%d+)$'},gnuplot={'^"(.-)", line (%d+): (.+)$'},go={'^(.-):(%d+):(%d+): (.+)$'},groovy={'^%s+at .-%((.-):(%d+)%)$','^(.-):(%d+): (.+)$'},haskell={'^(.-):(%d+):(%d+):%s*(.*)$'},icon={'^File (.-); Line (%d+) # (.+)$','^.-from line (%d+) in (.-)$'},java={'^%s+at .-%((.-):(%d+)%)$','^(.-):(%d+): (.+)$'},javascript={'^%s+at .-%((.-):(%d+):(%d+)%)$','^%s+at (.-):(%d+):(%d+)$','^(.-):(%d+):?$'},jq={'^jq: error: (.+) at (.-), line (%d+)'},julia={'^%s+%[%d+%].- at (.-):(%d+)$'},ltx={'^(.-):(%d+): (.+)$'},less={'^(.+) in (.-) on line (%d+), column (%d+):$'},lilypond={'^(.-):(%d+):(%d+):%s*(.*)$'},litcoffee={'^(.-):(%d+):(%d+): (.+)$'},lua={'^luac?: (.-):(%d+): (.+)$'},makefile={'^(.-):(%d+): (.+)$'},nemerle={'^(.-)%((%d+),(%d+)%): (.+)$'},nim={'^(.-)%((%d+), (%d+)%) (%w+:.+)$'},objective_c={'^(.-):(%d+):(%d+): (.+)$'},pascal={'^(.-)%((%d+),(%d+)%) (%w+:.+)$'},perl={'^(.+) at (.-) line (%d+)'},php={'^(.+) in (.-) on line (%d+)$'},pike={'^(.-):(%d+):(.+)$'},pony={'^(.-):(%d+):(%d+): (.+)$'},prolog={'^(.-):(%d+):(%d+): (.+)$','^(.-):(%d+): (.+)$'},pure={'^(.-), line (%d+): (.+)$'},python={'^%s*File "(.-)", line (%d+)'},rexx={'^Error %d+ running "(.-)", line (%d+): (.+)$'},ruby={'^%s+from (.-):(%d+):','^(.-):(%d+):%s*(.+)$'},rust={'^(.-):(%d+):(%d+): (.+)$',"panicked at '([^']+)', (.-):(%d+)"},sass={'^WARNING on line (%d+) of (.-):$','^%s+on line (%d+) of (.-)$'},scala={'^%s+at .-%((.-):(%d+)%)$','^(.-):(%d+): (.+)$'},sh={'^(.-): (%d+): %1: (.+)$'},bash={'^(.-): line (%d+): (.+)$'},zsh={'^(.-):(%d+): (.+)$'},smalltalk={'^(.-):(%d+): (.+)$','%((.-):(%d+)%)$'},snobol4={'^(.-):(%d+): (.+)$'},tcl={'^%s*%(file "(.-)" line (%d+)%)$'},tex={'^(.-):(%d+): (.+)$'},vala={'^(.-):(%d+)%.(%d+)[%-%.%d]+: (.+)$','^(.-):(%d+):(%d+): (.+)$'},vb={'^(.-)%((%d+),(%d+)%): (.+)$'},xs={'^(.-):(%d+)%S* (.+)$'},zig={'^(.-):(%d+):(%d+): (.+)$'}} 444-- Note: APDL,IDL,REBOL,RouterOS,Spin,Verilog,VHDL are proprietary. 445-- Note: ASP,CSS,Desktop,diff,django,elm,fstab,gettext,Gtkrc,HTML,ini,JSON,JSP,Markdown,Networkd,Postscript,Properties,R,Reason,RHTML,Systemd,XML don't have parse-able errors. 446-- Note: Batch,BibTeX,ConTeXt,Dockerfile,GLSL,Inform,Io,Lisp,MoonScript,Scheme,SQL,TeX cannot be parsed for one reason or another. 447 448--- 449-- Jumps to the source of the recognized compile/run warning or error on line 450-- number *line_num* in the message buffer. 451-- If *line_num* is `nil`, jumps to the next or previous warning or error, 452-- depending on boolean *next*. Displays an annotation with the warning or error 453-- message if possible. 454-- @param line_num Optional line number in the message buffer that contains the 455-- compile/run warning or error to go to. This parameter may be omitted 456-- completely. 457-- @param next Optional flag indicating whether to go to the next recognized 458-- warning/error or the previous one. Only applicable when *line_num* is 459-- `nil`. 460-- @see error_patterns 461-- @name goto_error 462function M.goto_error(line_num, next) 463 if type(line_num) == 'boolean' then line_num, next = nil, line_num end 464 local msg_view, msg_buf = nil, nil 465 for i = 1, #_VIEWS do 466 if is_msg_buf(_VIEWS[i].buffer) then msg_view = _VIEWS[i] break end 467 end 468 for i = 1, #_BUFFERS do 469 if is_msg_buf(_BUFFERS[i]) then msg_buf = _BUFFERS[i] break end 470 end 471 if not msg_view and not msg_buf then return end 472 if msg_view then ui.goto_view(msg_view) else view:goto_buffer(msg_buf) end 473 474 -- If no line number was given, find the next warning or error marker. 475 if not assert_type(line_num, 'number/nil', 1) and next ~= nil then 476 local f = next and buffer.marker_next or buffer.marker_previous 477 line_num = buffer:line_from_position(buffer.current_pos) 478 local WARN_BIT, ERROR_BIT = 1 << M.MARK_WARNING - 1, 1 << M.MARK_ERROR - 1 479 local wrapped = false 480 ::retry:: 481 local wline = f(buffer, line_num + (next and 1 or -1), WARN_BIT) 482 local eline = f(buffer, line_num + (next and 1 or -1), ERROR_BIT) 483 if wline == -1 and eline == -1 then 484 wline = f(buffer, next and 1 or buffer.line_count, WARN_BIT) 485 eline = f(buffer, next and 1 or buffer.line_count, ERROR_BIT) 486 elseif wline == -1 or eline == -1 then 487 if wline == -1 then wline = eline else eline = wline end 488 end 489 line_num = (next and math.min or math.max)(wline, eline) 490 if line_num == -1 and not wrapped then 491 line_num = next and 1 or buffer.line_count 492 wrapped = true 493 goto retry 494 end 495 end 496 497 -- Goto the warning or error and show an annotation. 498 local line = buffer:get_line(line_num):match('^[^\r\n]*') 499 local detail = scan_for_error(line:iconv(_CHARSET, 'UTF-8')) 500 if not detail then return end 501 buffer:goto_line(line_num) 502 textadept.editing.select_line() 503 if not detail.filename:find(not WIN32 and '^/' or '^%a:[/\\]') and cwd then 504 detail.filename = cwd .. (not WIN32 and '/' or '\\') .. detail.filename 505 end 506 local sloppy = not detail.filename:find(not WIN32 and '^/' or '^%a:[/\\]') 507 ui.goto_file(detail.filename, true, preferred_view, sloppy) 508 textadept.editing.goto_line(detail.line) 509 if detail.column then 510 buffer:goto_pos(buffer:find_column(detail.line, detail.column)) 511 end 512 if not detail.message then return end 513 buffer.annotation_text[detail.line] = detail.message 514 if detail.warning then return end 515 buffer.annotation_style[detail.line] = buffer:style_of_name('error') 516end 517events.connect(events.KEYPRESS, function(code) 518 if keys.KEYSYMS[code] == '\n' and is_msg_buf(buffer) and 519 scan_for_error(buffer:get_cur_line():match('^[^\r\n]*')) then 520 M.goto_error(buffer:line_from_position(buffer.current_pos)) 521 return true 522 end 523end) 524events.connect(events.DOUBLE_CLICK, function(_, line) 525 if is_msg_buf(buffer) then M.goto_error(line) end 526end) 527 528return M 529