~ubuntu-branches/ubuntu/wily/lua-lgi/wily

« back to all changes in this revision

Viewing changes to samples/gtkterminal.lua

  • Committer: Package Import Robot
  • Author(s): Enrico Tassi
  • Date: 2012-04-02 13:16:07 UTC
  • Revision ID: package-import@ubuntu.com-20120402131607-qcd5l8n9rx8lsq59
Tags: upstream-0.4+29+g74cbbb1
ImportĀ upstreamĀ versionĀ 0.4+29+g74cbbb1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/env lua
 
2
 
 
3
--
 
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).
 
7
--
 
8
 
 
9
local lgi = require 'lgi'
 
10
local Gtk = lgi.require('Gtk', '3.0')
 
11
local Vte = lgi.require('Vte', '2.90')
 
12
 
 
13
-- Simple readline implementation with asynchronous interface.
 
14
local ReadLine = {}
 
15
ReadLine.__index = ReadLine
 
16
 
 
17
function ReadLine.new()
 
18
   return setmetatable(
 
19
      {
 
20
         insert_mode = true,
 
21
         columns = 80,
 
22
         history = {},
 
23
      }, ReadLine)
 
24
end
 
25
 
 
26
function ReadLine:start_line(prompt)
 
27
   self.input = ''
 
28
   self.pos = 1
 
29
   self.prompt = prompt or ''
 
30
   self.history_pos = #self.history + 1
 
31
   self.display(self.prompt)
 
32
end
 
33
 
 
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)
 
38
end
 
39
 
 
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)
 
48
      if oldl ~= newl then
 
49
         rl.display(('\27[%dA'):format(oldl - newl))
 
50
      end
 
51
      if oldc ~= newc then
 
52
         rl.display(('\27[%d%s'):format(math.abs(newc - oldc),
 
53
                                        oldc < newc and 'C' or 'D'))
 
54
      end
 
55
   elseif newpos > rl.pos then
 
56
      -- Redraw portion between old and new cursor.
 
57
      rl.display(rl.input:sub(rl.pos, newpos - 1))
 
58
   end
 
59
   rl.pos = newpos
 
60
   if modified then
 
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')
 
64
   end
 
65
end
 
66
 
 
67
local bindings = {}
 
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)
 
73
   end
 
74
end
 
75
function bindings.enter(rl)
 
76
   redisplay(rl, #rl.input + 1)
 
77
   rl.display('\n')
 
78
   rl.commit(rl.input)
 
79
end
 
80
function bindings.back(rl)
 
81
   if rl.pos > 1 then redisplay(rl, rl.pos - 1) end
 
82
end
 
83
function bindings.forward(rl)
 
84
   if rl.pos <= #rl.input then redisplay(rl, rl.pos + 1) end
 
85
end
 
86
function bindings.home(rl)
 
87
   if rl.pos ~= 1 then redisplay(rl, 1) end
 
88
end
 
89
function bindings.goto_end(rl)
 
90
   if rl.pos ~= #rl.input then redisplay(rl, #rl.input + 1) end
 
91
end
 
92
function bindings.backspace(rl)
 
93
   if rl.pos > 1 then
 
94
      rl.input = rl.input:sub(1, rl.pos - 2) .. rl.input:sub(rl.pos, -1)
 
95
      redisplay(rl, rl.pos - 1, true)
 
96
   end
 
97
end
 
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)
 
102
   end
 
103
end
 
104
function bindings.kill(rl)
 
105
   rl.input = rl.input:sub(1, rl.pos - 1)
 
106
   redisplay(rl, rl.pos, true)
 
107
end
 
108
function bindings.clear(rl)
 
109
   rl.input = ''
 
110
   rl.history_pos = #rl.history + 1
 
111
   redisplay(rl, 1, true)
 
112
end
 
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)
 
117
end
 
118
function bindings.up(rl)
 
119
   if rl.history_pos > 1 then
 
120
      rl.history_pos = rl.history_pos - 1
 
121
      set_history(rl)
 
122
   end
 
123
end
 
124
function bindings.down(rl)
 
125
   if rl.history_pos <= #rl.history then
 
126
      rl.history_pos = rl.history_pos + 1
 
127
      set_history(rl)
 
128
   end
 
129
end
 
130
 
 
131
-- Real keys are here bound to symbolic names.
 
132
local function ctrl(char)
 
133
   return string.char(char:byte() - ('a'):byte() + 1)
 
134
end
 
135
 
 
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
 
154
 
 
155
function ReadLine:receive(key)
 
156
   (bindings[key] or bindings.default)(self, key)
 
157
end
 
158
 
 
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
 
163
   end
 
164
end
 
165
 
 
166
-- Instantiate terminal widget and couple it with our custom readline.
 
167
local terminal = Vte.Terminal {
 
168
   delete_binding = Vte.TerminalEraseBinding.ASCII_DELETE,
 
169
}
 
170
local readline = ReadLine.new()
 
171
 
 
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
 
176
   -- resize events.
 
177
   function terminal:on_size_allocate(rect)
 
178
      readline.columns = self:get_column_count()
 
179
   end
 
180
end
 
181
 
 
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)
 
188
end
 
189
function terminal:on_commit(str, length)
 
190
   readline.columns = self:get_column_count()
 
191
   readline:receive(str)
 
192
end
 
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')
 
197
   if chunk then
 
198
      (function(ok, ...)
 
199
          if not ok then
 
200
             answer = tostring(...)
 
201
          else
 
202
             answer = {}
 
203
             for i = 1, select('#', ...) do
 
204
                answer[#answer + 1] = tostring(select(i, ...))
 
205
             end
 
206
             answer = #answer > 0 and table.concat(answer, '\t')
 
207
          end
 
208
    end)(pcall(chunk))
 
209
   end
 
210
   if answer then
 
211
      readline.display(answer .. '\n')
 
212
   end
 
213
 
 
214
   -- Store the line into rl history and start reading new line.
 
215
   readline:add_line(line)
 
216
   readline:start_line(_PROMPT or '> ')
 
217
end
 
218
 
 
219
-- Create the application.
 
220
local app = Gtk.Application { application_id = 'org.lgi.samples.gtkconsole' }
 
221
 
 
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,
 
228
         })
 
229
   terminal.expand = true
 
230
   readline.display [[
 
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:
 
236
 
 
237
Gtk = lgi.Gtk <Enter>
 
238
window = Gtk.Window { title = 'Test' } <Enter>
 
239
window:show_all() <Enter>
 
240
window.title = 'Different' <Enter>
 
241
 
 
242
]]
 
243
   local window = Gtk.Window {
 
244
      application = self,
 
245
      title = 'Lua Terminal',
 
246
      default_width = 640,
 
247
      default_height = 480,
 
248
      has_resize_grip = true,
 
249
      child = grid,
 
250
   }
 
251
   window:show_all()
 
252
   readline.columns = terminal:get_column_count()
 
253
   readline:start_line(_PROMPT or '> ')
 
254
 
 
255
   -- For convenience, propagate 'lgi' into the global namespace.
 
256
   _G.lgi = lgi
 
257
end
 
258
 
 
259
-- Start the application.
 
260
app:run { arg[0], ... }