aboutsummaryrefslogtreecommitdiff
path: root/modules/textadept/run.lua
blob: f60bf2b5bdca9e862141ed3bbc5d3302108cc0fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
-- Copyright 2007-2009 Mitchell mitchell<att>caladbolg.net. See LICENSE.

local textadept = _G.textadept

---
-- Module for running/executing source files.
module('_m.textadept.run', package.seeall)

---
-- [Local function] Prints a command and its output to Textadept.
-- @param cmd The OS command executed.
-- @param output The output from that OS command.
local function print_command(cmd, output)
  textadept.print('> '..cmd..'\n'..output)
  buffer:goto_pos(buffer.length)
end

---
-- Passes the current file to a specified compiler to run with the given flags
-- and prints the output to Textadept.
-- @param compiler The system's compiler for the file.
-- @param cflags A string of flags to pass to the interpreter.
-- @param args Table of arguments key keys as follows:
--   * filename_noext_with_flag The value of this flag passed to the compiler
--                              is the filename without its extension.
function compiler(compiler, cflags, args)
  if type(cflags) ~= 'string' then cflags = '' end
  if not args then args = {} end
  local file = buffer.filename
  if args.filename_noext_with_flag then
    local filename_noext = file:match('^(.+)%.')
    local flag = args.filename_noext_with_flag
    cflags = string.format('%s %s"%s"', cflags, flag, filename_noext)
  end
  local command = string.format('%s %s "%s" 2>&1', compiler, cflags, file)
  local p = io.popen(command)
  local out = p:read('*all')
  p:close()
  print_command(command, out)
end

---
-- Passes the current file to a specified interpreter to run with the given
-- flags and prints the output to Textadept.
-- @param interpreter The system's interpreter for the file.
-- @param flags A string of flags to pass to the interpreter.
-- @param args Table of arguments with keys as follows:
--   * noext Do not include the filename's extension when passing to the
--           interpreter.
--   * nopath Do not include the full filepath, only the file's name.
--   * nopath_path_with_flag Same as nopath, but use the path as the value of
--                           a flag passed to the interpreter.
function interpreter(interpreter, flags, args)
  if type(flags) ~= 'string' then flags = '' end
  if not args then args = {} end
  local file = buffer.filename
  if args.noext then file = file:match('^(.+)%.') end
  if args.nopath then file = file:match('[^/\\]+$') end
  if args.nopath_path_with_flag then
    local path = file:match('^.+/')
    local flag = args.nopath_path_with_flag
    flags = string.format('%s %s"%s"', flags, flag, path)
    file = file:match('[^/\\]+$')
  end
  local command = string.format('%s %s "%s" 2>&1', interpreter, flags, file)
  local p = io.popen(command)
  local out = p:read('*all')
  p:close()
  print_command(command, out)
end

-- TODO: makefile
-- TODO: etc.

---
-- [Local table] File extensions and their associated 'compile' actions.
-- Each key is a file extension whose value is a table with the compile function
-- and parameters given as an ordered list.
-- @class table
-- @name compile_for_ext
local compile_for_ext = {
  c = { compiler, 'gcc', '-pedantic -Os',
        { filename_noext_with_flag = '-o ' } },
  cpp = { compiler, 'g++', '-pedantic -Os',
          { filename_noext_with_flag = '-o ' } },
  java = { compiler, 'javac' },
}

---
-- Compiles the file as specified by its extension in the compile_for_ext table.
-- @see compile_for_ext
function compile()
  if not buffer.filename then return end
  local ext = buffer.filename:match('[^.]+$')
  local action = compile_for_ext[ext]
  if not action then return end
  local f, args = action[1], { unpack(action) }
  table.remove(args, 1) -- function
  f(unpack(args))
end

---
-- [Local table] File extensions and their associated 'go' actions.
-- Each key is a file extension whose value is a table with the run function
-- and parameters given as an ordered list.
-- @class table
-- @name go_for_ext
local go_for_ext = {
  c = { interpreter, '', '', { noext = true } },
  cpp = { interpreter, '', '', { noext = true } },
  java = { interpreter, 'java', '',
           { noext = true, nopath_path_with_flag = '-cp '  } },
  lua = { interpreter, 'lua' },
  pl = { interpreter, 'perl' },
  php = { interpreter, 'php', '-f' },
  py = { interpreter, 'python' },
  rb = { interpreter, 'ruby' },
}

---
-- Runs/executes the file as specified by its extension in the go_for_ext table.
-- @see go_for_ext
function go()
  if not buffer.filename then return end
  local ext = buffer.filename:match('[^.]+$')
  local action = go_for_ext[ext]
  if not action then return end
  local f, args = action[1], { unpack(action) }
  table.remove(args, 1) -- function
  f(unpack(args))
end

---
-- [Local table] A table of error string details.
-- Each entry is a table with the following fields:
--   pattern: the Lua pattern that matches a specific error string.
--   filename: the index of the Lua capture that contains the filename the error
--     occured in.
--   line: the index of the Lua capture that contains the line number the error
--     occured on.
--   message: [Optional] the index of the Lua capture that contains the error's
--     message. A call tip will be displayed if a message was captured.
-- When an error message is double-clicked, the user is taken to the point of
--   error.
-- @class table
-- @name error_details
local error_details = {
  -- c, c++, and java errors and warnings have the same format as ruby ones
  lua = {
    pattern = '^lua: (.-):(%d+): (.+)$',
    filename = 1, line = 2, message = 3
  },
  perl = {
    pattern = '^(.+) at (.-) line (%d+)',
    message = 1, filename = 2, line = 3
  },
  php_error = {
    pattern = '^Parse error: (.+) in (.-) on line (%d+)',
    message = 1, filename = 2, line = 3
  },
  php_warning = {
    pattern = '^Warning: (.+) in (.-) on line (%d+)',
    message = 1, filename = 2, line = 3
  },
  python = {
    pattern = '^%s*File "([^"]+)", line (%d+)',
    filename = 1, line = 2
  },
  ruby = {
    pattern = '^(.-):(%d+): (.+)$',
    filename = 1, line = 2, message = 3
  },
}

---
-- When the user double-clicks an error message, go to the line in the file
-- the error occured at and display a calltip with the error message.
-- @param pos The position of the caret.
-- @param line_num The line double-clicked.
-- @see error_details
function goto_error(pos, line_num)
  if buffer.shows_messages or buffer.shows_errors then
    line = buffer:get_line(line_num)
    for _, error_detail in pairs(error_details) do
      local captures = { line:match(error_detail.pattern) }
      if #captures > 0 then
        local lfs = require 'lfs'
        local filename = captures[error_detail.filename]
        if lfs.attributes(filename) then
          textadept.io.open(filename)
          _m.textadept.editing.goto_line(captures[error_detail.line])
          local msg = captures[error_detail.message]
          if msg then buffer:call_tip_show(buffer.current_pos, msg) end
        else
          error(string.format(
            textadept.locale.M_TEXTADEPT_RUN_FILE_DOES_NOT_EXIST, filename))
        end
        break
      end
    end
  end
end
textadept.events.add_handler('double_click', goto_error)