4
-- Lua console using Vte widget. It uses homegrown poor-man's
5
-- Lua-only readline implementation (most of the code of this sample,
6
-- not really related to GLib/Gtk in any way).
9
local lgi = require 'lgi'
10
local Gtk = lgi.require('Gtk', '3.0')
11
local Vte = lgi.require('Vte', '2.90')
13
-- Simple readline implementation with asynchronous interface.
15
ReadLine.__index = ReadLine
17
function ReadLine.new()
26
function ReadLine:start_line(prompt)
29
self.prompt = prompt or ''
30
self.history_pos = #self.history + 1
31
self.display(self.prompt)
34
-- Translates input string position into line/column pair.
35
local function getpos(rl, pos)
36
local full, part = math.modf((pos + #rl.prompt - 1) / rl.columns)
37
return full, math.floor(part * rl.columns + 0.5)
40
-- Redisplays currently edited line, moves cursor to newpos, assumes
41
-- that rl.input is updated with new contents but rl.pos still holds
42
-- old cursor position.
43
local function redisplay(rl, newpos, modified)
44
if newpos < rl.pos then
45
-- Go back with the cursor
46
local oldl, oldc = getpos(rl, rl.pos)
47
local newl, newc = getpos(rl, newpos)
49
rl.display(('\27[%dA'):format(oldl - newl))
52
rl.display(('\27[%d%s'):format(math.abs(newc - oldc),
53
oldc < newc and 'C' or 'D'))
55
elseif newpos > rl.pos then
56
-- Redraw portion between old and new cursor.
57
rl.display(rl.input:sub(rl.pos, newpos - 1))
61
-- Save cursor, redraw the rest of the string, clear the rest of
62
-- the line and screen and restore cursor position back.
63
rl.display('\27[s' .. rl.input:sub(newpos, -1) .. '\27[K\27[J\27[u')
68
function bindings.default(rl, key)
69
if not key:match('%c') then
70
rl.input = rl.input:sub(1, rl.pos - 1) .. key
71
.. rl.input:sub(rl.pos + (rl.insert_mode and 0 or 1), -1)
72
redisplay(rl, rl.pos + 1, rl.insert_mode)
75
function bindings.enter(rl)
76
redisplay(rl, #rl.input + 1)
80
function bindings.back(rl)
81
if rl.pos > 1 then redisplay(rl, rl.pos - 1) end
83
function bindings.forward(rl)
84
if rl.pos <= #rl.input then redisplay(rl, rl.pos + 1) end
86
function bindings.home(rl)
87
if rl.pos ~= 1 then redisplay(rl, 1) end
89
function bindings.goto_end(rl)
90
if rl.pos ~= #rl.input then redisplay(rl, #rl.input + 1) end
92
function bindings.backspace(rl)
94
rl.input = rl.input:sub(1, rl.pos - 2) .. rl.input:sub(rl.pos, -1)
95
redisplay(rl, rl.pos - 1, true)
98
function bindings.delete(rl)
99
if rl.pos <= #rl.input then
100
rl.input = rl.input:sub(1, rl.pos - 1) .. rl.input:sub(rl.pos + 1, -1)
101
redisplay(rl, rl.pos, true)
104
function bindings.kill(rl)
105
rl.input = rl.input:sub(1, rl.pos - 1)
106
redisplay(rl, rl.pos, true)
108
function bindings.clear(rl)
110
rl.history_pos = #rl.history + 1
111
redisplay(rl, 1, true)
113
local function set_history(rl)
114
rl.input = rl.history[rl.history_pos] or ''
115
redisplay(rl, 1, true)
116
redisplay(rl, #rl.input + 1)
118
function bindings.up(rl)
119
if rl.history_pos > 1 then
120
rl.history_pos = rl.history_pos - 1
124
function bindings.down(rl)
125
if rl.history_pos <= #rl.history then
126
rl.history_pos = rl.history_pos + 1
131
-- Real keys are here bound to symbolic names.
132
local function ctrl(char)
133
return string.char(char:byte() - ('a'):byte() + 1)
136
bindings[ctrl'b'] = bindings.back
137
bindings['\27[D'] = bindings.back
138
bindings[ctrl'f'] = bindings.forward
139
bindings['\27[C'] = bindings.forward
140
bindings[ctrl'a'] = bindings.home
141
bindings['\27OH'] = bindings.home
142
bindings[ctrl'e'] = bindings.goto_end
143
bindings['\27OF'] = bindings.goto_end
144
bindings[ctrl'h'] = bindings.backspace
145
bindings[ctrl'd'] = bindings.delete
146
bindings['\127'] = bindings.delete
147
bindings[ctrl'k'] = bindings.kill
148
bindings[ctrl'c'] = bindings.clear
149
bindings[ctrl'p'] = bindings.up
150
bindings['\27[A'] = bindings.up
151
bindings[ctrl'n'] = bindings.down
152
bindings['\27[B'] = bindings.down
153
bindings['\r'] = bindings.enter
155
function ReadLine:receive(key)
156
(bindings[key] or bindings.default)(self, key)
159
function ReadLine:add_line(line)
160
-- Avoid duplicating lines in history.
161
if self.history[#self.history] ~= line then
162
self.history[#self.history + 1] = line
166
-- Instantiate terminal widget and couple it with our custom readline.
167
local terminal = Vte.Terminal {
168
delete_binding = Vte.TerminalEraseBinding.ASCII_DELETE,
170
local readline = ReadLine.new()
172
if Vte.Terminal.on_size_allocate then
173
-- 'size_allocate' signal is not present in some older Gtk-3.0.gir files
174
-- due to bug in older GI versions. Make sure that this does not trip us
175
-- completely, it only means that readline will not react on the terminal
177
function terminal:on_size_allocate(rect)
178
readline.columns = self:get_column_count()
182
function readline.display(str)
183
-- Make sure that \n is always replaced with \r\n. Also make sure
184
-- that after \n, kill-rest-of-line is always issued, so that
185
-- random garbage does not stay on the screen.
186
str = str:gsub('([^\r]?)\n', '%1\r\n'):gsub('\r\n', '\27[K\r\n')
187
terminal:feed(str, #str)
189
function terminal:on_commit(str, length)
190
readline.columns = self:get_column_count()
191
readline:receive(str)
193
function readline.commit(line)
194
-- Try to execute input line.
195
line = line:gsub('^%s?(=)%s*', 'return ')
196
local chunk, answer = (loadstring or load)(line, '=stdin')
200
answer = tostring(...)
203
for i = 1, select('#', ...) do
204
answer[#answer + 1] = tostring(select(i, ...))
206
answer = #answer > 0 and table.concat(answer, '\t')
211
readline.display(answer .. '\n')
214
-- Store the line into rl history and start reading new line.
215
readline:add_line(line)
216
readline:start_line(_PROMPT or '> ')
219
-- Create the application.
220
local app = Gtk.Application { application_id = 'org.lgi.samples.gtkconsole' }
222
-- Pack terminal into the window with scrollbar.
223
function app:on_activate()
224
local grid = Gtk.Grid { child = terminal }
225
grid:add(Gtk.Scrollbar {
226
orientation = Gtk.Orientation.VERTICAL,
227
adjustment = terminal.adjustment,
229
terminal.expand = true
231
This is terminal emulation of standard Lua console. Enter Lua
232
commands as in interactive Lua console. The advantage over standard
233
console is that in this context, GMainLoop is running, so this
234
console is ideal for interactive toying with Gtk (and other
235
mainloop-based) components. Try following:
237
Gtk = lgi.Gtk <Enter>
238
window = Gtk.Window { title = 'Test' } <Enter>
239
window:show_all() <Enter>
240
window.title = 'Different' <Enter>
243
local window = Gtk.Window {
245
title = 'Lua Terminal',
247
default_height = 480,
248
has_resize_grip = true,
252
readline.columns = terminal:get_column_count()
253
readline:start_line(_PROMPT or '> ')
255
-- For convenience, propagate 'lgi' into the global namespace.
259
-- Start the application.
260
app:run { arg[0], ... }