aboutsummaryrefslogtreecommitdiff
path: root/modules/textadept/run.lua
blob: f95b56f712e7548cae80e2774ebc010cca36231d (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
-- Copyright 2007-2011 Mitchell mitchell<att>caladbolg.net. See LICENSE.

local L = _G.locale.localize
local events = _G.events

---
-- Module for running/executing source files.
-- Typically, language-specific modules populate the 'compile_command',
-- 'run_command', and 'error_detail' tables for a particular language's file
-- extension.
module('_m.textadept.run', package.seeall)

-- Markdown:
-- ## Run Events
--
-- * `_G.events.COMPILE_OUTPUT`: Called after a compile command is executed.
--   When connecting to this event (typically from a language-specific module),
--   connect with an index of 1 and return `true` if the event was handled and
--   you want to override the default handler that prints the output to a new
--   view.<br />
--       * `lexer`: The lexer language.
--       * `output`: The output from the command.
-- * `_G.events.RUN_OUTPUT`: Called after a run command is executed. When
--   connecting to this event (typically from a language-specific module),
--   connect with an index of 1 and return `true` if the event was handled and
--   you want to override the default handler that prints the output to a new
--   view.<br />
--       * `lexer`: The lexer language.
--       * `output`: The output from the command.

-- Events.
events.COMPILE_OUTPUT = 'compile_output'
events.RUN_OUTPUT = 'run_output'

---
-- Executes the command line parameter and prints the output to Textadept.
-- @param command The command line string.
--   It can have the following macros:
--     * %(filepath) The full path of the current file.
--     * %(filedir) The current file's directory path.
--     * %(filename) The name of the file including extension.
--     * %(filename_noext) The name of the file excluding extension.
function execute(command)
  local filepath = buffer.filename:iconv(_CHARSET, 'UTF-8')
  local filedir, filename = '', filepath
  if filepath:find('[/\\]') then
    filedir, filename = filepath:match('^(.+[/\\])([^/\\]+)$')
  end
  local filename_noext = filename:match('^(.+)%.')
  command = command:gsub('%%%b()', {
    ['%(filepath)'] = filepath,
    ['%(filedir)'] = filedir,
    ['%(filename)'] = filename,
    ['%(filename_noext)'] = filename_noext,
  })
  local current_dir = lfs.currentdir()
  lfs.chdir(filedir)
  local p = io.popen(command..' 2>&1')
  local out = p:read('*all')
  p:close()
  lfs.chdir(current_dir)
  return ('> '..command..'\n'..out):iconv('UTF-8', _CHARSET)
end

-- Executes a compile or run command.
-- @param cmd_table Either compile_command or run_command
local function command(cmd_table)
  if not buffer.filename then return end
  buffer:save()
  local action = cmd_table[buffer.filename:match('[^.]+$')]
  if action then
    return execute(type(action) == 'function' and action() or action)
  end
end

---
-- File extensions and their associated 'compile' actions.
-- Each key is a file extension whose value is a either a command line string to
-- execute or a function returning one.
-- This table is typically populated by language-specific modules.
-- @class table
-- @name compile_command
compile_command = {}

---
-- Compiles the file as specified by its extension in the compile_command
-- table.
-- @see compile_command
function compile()
  events.emit(events.COMPILE_OUTPUT, buffer:get_lexer(),
              command(compile_command))
end
events.connect(events.COMPILE_OUTPUT,
               function(lexer, output) gui.print(output) end)

---
-- File extensions and their associated 'go' actions.
-- Each key is a file extension whose value is either a command line string to
-- execute or a function returning one.
-- This table is typically populated by language-specific modules.
-- @class table
-- @name run_command
run_command = {}

---
-- Runs/executes the file as specified by its extension in the run_command
-- table.
-- @see run_command
function run()
  events.emit(events.RUN_OUTPUT, buffer:get_lexer(), command(run_command))
end
events.connect(events.RUN_OUTPUT,
               function(lexer, output) gui.print(output) end)

---
-- 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.
-- This table is usually populated by language-specific modules.
-- @class table
-- @name error_detail
error_detail = {}

---
-- 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_detail
function goto_error(pos, line_num)
  local type = buffer._type
  if type == L('[Message Buffer]') or type == L('[Error Buffer]') then
    line = buffer:get_line(line_num)
    for _, error_detail in pairs(error_detail) do
      local captures = { line:match(error_detail.pattern) }
      if #captures > 0 then
        local utf8_filename = captures[error_detail.filename]
        local filename = utf8_filename:iconv(_CHARSET, 'UTF-8')
        if lfs.attributes(filename) then
          io.open_file(utf8_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('"%s" %s', utf8_filename, L('does not exist')))
        end
        break
      end
    end
  end
end
events.connect(events.DOUBLE_CLICK, goto_error)