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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import GPS
import six
import sys
Shift_Mask = 1
Lock_Mask = 2
Control_Mask = 4
Mod1_Mask = 8
Mod2_Mask = 16
Key_Return = 65293
Key_Backspace = 65288
Key_Tab = 65289
Key_Left = 65361
Key_Up = 65362
Key_Right = 65363
Key_Down = 65364
Key_Escape = 65307
class Console_Process(GPS.Console, GPS.Process):
"""This class provides a way to spawn an interactive process and
do its input/output in a dedicated console in GPS.
The process is created so that it does not appear in the task
manager, and therefore the user can exit GPS without being
asked whether or not to kill the process.
You can of course derive from this class easily. Things are
slightly more complicated if you want in fact to derive from
a child of GPS.Console (for instance a class that would handle
ANSI escape sequences). The code would then look like::
class ANSI_Console (GPS.Console):
def write (self, txt): ...
class My_Process (ANSI_Console, Console_Process):
def __init__ (self, command):
Console_Process.__init__ (self, command)
In the list of base classes for My_Process, you must put
ANSI_Console before Console_Process. This is because python
resolves overridden methods by looking depth-first search from
left to right. This way, it will see ANSI_Console.write before
Console_Process.write and therefore use the former.
However, because of that the __init__ method that would be called
when calling My_Process (...) is also that of ANSI_Console.
Therefore you must define your own __init__ method locally.
See also the class ANSI_Console_Process if you need your process
to execute within a terminal that understands ANSI escape sequences.
:param boolean force: If True, a new console is opened, otherwise an
existing one will be reused (although you should take care in this
case if you have multiple processes attached to the same console).
:param boolean manage_prompt: If True, then GPS will do some higher level
handling of prompts: when some output is done by the process, GPS
will temporarily hide what the user was typing, insert the output,
and append what the user was typing. This is in general suitable but
might interfer with external programs that do their own screen
management through ANSI commands (like a Unix shell for instance).
:param boolean task_manager: If True, the process will be visible in the
GPS tasks view and can be interrupted or paused by users.
Otherwise, it is running in the background and never visible to the
user.
"""
def __init__(self, command, close_on_exit=True, force=False,
ansi=False, manage_prompt=True, task_manager=False):
self.close_on_exit = close_on_exit
try:
GPS.Console.__init__(
self,
command[0],
manage_prompt=manage_prompt,
on_input=self.on_input,
on_destroy=Console_Process.on_destroy,
on_resize=self.on_resize,
on_interrupt=Console_Process.on_interrupt,
on_completion=self.on_completion,
on_key=self.on_key,
ansi=ansi,
force=force)
GPS.Process.__init__(
self,
command=command,
regexp='.+',
single_line_regexp=True, # For efficiency
strip_cr=not ansi, # if ANSI terminal, CR is irrelevant
task_manager=task_manager,
on_exit=self.on_exit,
on_match=self.on_output)
GPS.MDI.get_by_child(self).raise_window()
except:
GPS.Console().write(str(sys.exc_info()[1]) + '\n')
try:
self.destroy()
self.kill()
except:
pass
GPS.Console().write('Could not spawn: %s\n' % (' '.join(command)))
def on_output(self, matched, unmatched):
"""This method is called when the process has emitted some output.
The output is then printed to the console
"""
self.write(unmatched + matched)
def on_exit(self, status, remaining_output):
"""This method is called when the process terminates.
As a result, we close the console automatically, although we could
decide to keep it open as well
"""
try:
if self.close_on_exit:
self.destroy() # Close console
else:
self.write(remaining_output)
self.write('\nexit status: %s' % status)
except:
pass # Might have already been destroyed if that's what
# resulted in the call to on_exit
def on_input(self, input):
"""This method is called when the user has pressed <enter> in the
console. The corresponding command is then sent to the process
"""
self.send(input)
def on_destroy(self):
"""This method is called when the console is being closed.
As a result, we terminate the process (this also results in a
call to on_exit
"""
self.kill()
def on_resize(self, console, rows, columns=None):
"""This method is called when the console is being resized. We then
let the process know about the size of its terminal, so that it
can adapt its output accordingly. This is especially useful with
processes like gdb or unix shells
"""
# ??? There is an issue here, since this subprogram seems to be called
# sometimes with 3 parameters, sometimes with 4: for sure, Ada calls it
# with 3 parameters, but since it was passed to the GPS.Console
# constructor as "self.on_resize", there is one extra arg for self.
if isinstance(console, int):
columns = rows
rows = console
self.set_size(rows, columns)
def on_interrupt(self):
"""This method is called when the user presses control-c in the
console. This interrupts the command we are currently processing
"""
self.interrupt()
def on_completion(self, input):
"""The user has pressed <tab> in the console. The default is just to
insert the \t character, but if you are driving a process that knows
about completion, such as an OS shell for instance, you could have
a different implementation. input is the full input till, but not
including, the tab character
"""
self.write('\t')
def on_key(self, keycode, key, modifier):
"""The user has pressed a key in the console (any key). This is called
before any of the higher level on_completion or on_input callbacks.
If this subprogram returns True, GPS will consider that the key has
already been handled and will not do its standard processing with
it. By default, we simply let the key through and let GPS handle it.
:param key: the unicode character (numeric value) that was entered
by the user. _modifier_ is a mask of the control and shift keys
that were pressed at the same time. See the Mask constants above.
keycode is the code of the key, which is useful for non-printable
characters. It is set to 0 in some cases if the input is
simulated after the user has copied some text into the console
This function is also called for each character pasted by the user
in the console. If it returns True, then the selection will not be
inserted in the console.
"""
return False
class ANSI_Console_Process(Console_Process):
"""This class has a purpose similar to Console_Process.
However, this class does not attempt to do any of the high-level
processing of prompt and input that Console_Process does, and instead
forward immediately any of the key strokes within the console directly
to the external process.
It also provides an ANSI terminal to the external process. The latter
can thus send escape sequences to change colors, cursor position,...
"""
def __init__(self, command):
Console_Process.__init__(self, command, force=True, ansi=True,
manage_prompt=False)
def on_input(self, input):
# Do nothing, this was already handled when each key was pressed
pass
def on_completion(self, input):
# Do nothing, this was already handled when each key was pressed
pass
def __get_key_str(self, keycode, key):
"""Convert a key/keycode into a string that can be sent to an external
process"""
if keycode == Key_Return:
return b'\r'
elif keycode == Key_Tab:
return b'\t'
elif keycode == Key_Backspace:
return six.unichr(8)
elif key != 0:
return six.unichr(key).encode('utf8')
elif keycode == Key_Escape:
return b"\033"
elif keycode == Key_Left:
return b"\033[D"
elif keycode == Key_Right:
return b"\033[C"
elif keycode == Key_Up:
return b"\033[A"
elif keycode == Key_Down:
return b"\033[B"
else:
GPS.Logger('CONSOLE').log('keycode=%s key=%s' % (keycode, key))
return ''
def on_key(self, keycode, key, modifier):
if modifier == 0 or modifier == Shift_Mask:
self.send(self.__get_key_str(keycode, key), add_lf=False)
elif modifier == Control_Mask:
if key in range(six.byte2int('A'), six.byte2int('_') + 1):
self.send(six.int2byte(key - six.byte2int('A') + 1), add_lf=False)
elif key in range(six.byte2int('a'), six.byte2int('z') + 1):
# Same as pressing the upper-case equivalent
self.send(six.int2byte(key - six.byte2int('a') + 1), add_lf=False)
elif keycode == Key_Return or keycode == Key_Escape:
# Same as key without return
self.send('\n', add_lf=False)
else:
# Seems like most terminals just send ESC in such a case
self.send("\033", add_lf=False)
else:
GPS.Logger('CONSOLE').log('keycode=%s key=%s modifier=%s'
% (keycode, key, modifier))
return True
|