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

« back to all changes in this revision

Viewing changes to samples/console.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
-- Lua console implemented using Gtk widgets.
 
4
 
 
5
local lgi = require 'lgi'
 
6
local Gio = lgi.Gio
 
7
local Gtk = lgi.require('Gtk', '3.0')
 
8
local Gdk = lgi.require('Gdk', '3.0')
 
9
local Pango = lgi.Pango
 
10
local GtkSource = lgi.GtkSource
 
11
 
 
12
-- Creates new console instance.
 
13
local function Console()
 
14
   -- Define console object actions.
 
15
   local actions = {
 
16
      execute = Gtk.Action {
 
17
         name = 'execute', stock_id = Gtk.STOCK_OK, is_important = true,
 
18
         label = "_Execute", tooltip = "Execute",
 
19
      },
 
20
 
 
21
      multiline = Gtk.ToggleAction {
 
22
         name = 'multiline', stock_id = Gtk.STOCK_JUSTIFY_LEFT,
 
23
         is_important = true,
 
24
         label = "_Multiline",
 
25
         tooltip = "Switches command entry to multiline mode",
 
26
      },
 
27
 
 
28
      up = Gtk.Action {
 
29
         name = 'up', stock_id = Gtk.STOCK_GO_UP,
 
30
         label = "_Previous", tooltip = "Previous command in history",
 
31
         sensitive = false,
 
32
      },
 
33
 
 
34
      down = Gtk.Action {
 
35
         name = 'down', stock_id = Gtk.STOCK_GO_DOWN,
 
36
         label = "_Next", tooltip = "Next command in history",
 
37
         sensitive = false,
 
38
      },
 
39
 
 
40
      clear = Gtk.Action {
 
41
         name = 'clear', stock_id = Gtk.STOCK_CLEAR,
 
42
         label = "_Clear", tooltip = "Clear output window",
 
43
      },
 
44
 
 
45
      about = Gtk.Action {
 
46
         name = 'about', stock_id = Gtk.STOCK_ABOUT,
 
47
         label = "_About", tooltip = "About",
 
48
      },
 
49
 
 
50
      quit = Gtk.Action {
 
51
         name = 'quit', stock_id = Gtk.STOCK_QUIT,
 
52
         label = "_Quit", tooltip = "Quit",
 
53
      },
 
54
   }
 
55
 
 
56
   -- Define the widget tree.
 
57
   local widget = Gtk.Grid {
 
58
      orientation = Gtk.Orientation.VERTICAL,
 
59
      Gtk.Toolbar {
 
60
         Gtk.ToolButton { related_action = actions.clear },
 
61
         Gtk.SeparatorToolItem {},
 
62
         Gtk.ToolItem { Gtk.FontButton { id = 'font_button' } },
 
63
         Gtk.SeparatorToolItem {},
 
64
         Gtk.ToolButton { related_action = actions.about },
 
65
         Gtk.ToolButton { related_action = actions.quit },
 
66
      },
 
67
      Gtk.Paned {
 
68
         orientation = Gtk.Orientation.VERTICAL,
 
69
         {
 
70
            Gtk.ScrolledWindow {
 
71
               shadow_type = Gtk.ShadowType.IN,
 
72
               Gtk.TextView {
 
73
                  id = 'output', expand = true,
 
74
                  buffer = Gtk.TextBuffer {
 
75
                     tag_table = Gtk.TextTagTable {
 
76
                        Gtk.TextTag { name = 'command', foreground = 'blue' },
 
77
                        Gtk.TextTag { name = 'log' },
 
78
                        Gtk.TextTag { name = 'result',
 
79
                                      style = Pango.Style.ITALIC },
 
80
                        Gtk.TextTag { name = 'error',
 
81
                                      weight = Pango.Weight.BOLD,
 
82
                                      foreground = 'red' },
 
83
                     }
 
84
                  }
 
85
               }
 
86
            },
 
87
            resize = true
 
88
         },
 
89
         {
 
90
            Gtk.Grid {
 
91
               orientation = Gtk.Orientation.HORIZONTAL,
 
92
               {
 
93
                  Gtk.ScrolledWindow {
 
94
                     shadow_type = Gtk.ShadowType.IN,
 
95
                     GtkSource.View {
 
96
                        id = 'entry',
 
97
                        hexpand = true,
 
98
                        wrap_mode = Gtk.WrapMode.CHAR,
 
99
                        auto_indent = true,
 
100
                        tab_width = 4,
 
101
                        buffer = GtkSource.Buffer {
 
102
                           language = GtkSource.LanguageManager.get_default():
 
103
                           get_language('lua'),
 
104
                        },
 
105
                     }
 
106
                  }, 
 
107
                  height = 2
 
108
               },
 
109
               Gtk.Toolbar {
 
110
                  orientation = Gtk.Orientation.VERTICAL,
 
111
                  toolbar_style = Gtk.ToolbarStyle.ICONS,
 
112
                  vexpand = true,
 
113
                  Gtk.ToolButton { related_action = actions.execute },
 
114
                  Gtk.ToggleToolButton { related_action = actions.multiline },
 
115
                  Gtk.SeparatorToolItem {},
 
116
                  Gtk.ToolButton { related_action = actions.up },
 
117
                  Gtk.ToolButton { related_action = actions.down },
 
118
               },
 
119
               {
 
120
                  Gtk.Label {
 
121
                     id = 'indicator',
 
122
                     label = '1:1',
 
123
                     single_line_mode = true,
 
124
                     justify = Gtk.Justification.RIGHT,
 
125
                  }, 
 
126
                  left_attach = 1,
 
127
                  top_attach = 1
 
128
               },
 
129
            },
 
130
            resize = true
 
131
         }
 
132
      }
 
133
   }
 
134
 
 
135
   -- Cache important widgets in local variables
 
136
   local entry = widget.child.entry
 
137
   local output = widget.child.output
 
138
   local indicator = widget.child.indicator
 
139
   local font_button = widget.child.font_button
 
140
 
 
141
   -- When font changes, apply it to both views.
 
142
   font_button.on_notify['font-name'] = function(button)
 
143
      local desc = Pango.FontDescription.from_string(button.font_name)
 
144
      output:override_font(desc)
 
145
      entry:override_font(desc)
 
146
   end
 
147
 
 
148
   -- Initialize font.  Get preferred font from system settings, if
 
149
   -- they are installed.  GSettings crash the application if the
 
150
   -- schema is not found, so better check first if we can use it.
 
151
   font_button.font_name = 'Monospace'
 
152
   for _, schema in pairs(Gio.Settings.list_schemas()) do
 
153
      if schema == 'org.gnome.desktop.interface' then
 
154
         font_button.font_name = Gio.Settings(
 
155
            { schema = schema }):get_string('monospace-font-name')
 
156
         break
 
157
      end
 
158
   end
 
159
 
 
160
   -- Change indicator text when position in the entry changes.
 
161
   entry.buffer.on_notify['cursor-position'] = function(buffer)
 
162
      local iter = buffer:get_iter_at_mark(buffer:get_insert())
 
163
      indicator.label =
 
164
         iter:get_line() + 1 .. ':' .. iter:get_line_offset() + 1
 
165
   end
 
166
 
 
167
   local output_end_mark = output.buffer:create_mark(
 
168
      nil, output.buffer:get_end_iter(), false)
 
169
 
 
170
   -- Appends text to the output window, optionally with specified tag.
 
171
   local function append_output(text, tag)
 
172
      -- Append the text.
 
173
      local end_iter = output.buffer:get_end_iter()
 
174
      local offset = end_iter:get_offset()
 
175
      output.buffer:insert(end_iter, text, -1)
 
176
      end_iter = output.buffer:get_end_iter()
 
177
 
 
178
      -- Apply proper tag.
 
179
      tag = output.buffer.tag_table.tag[tag]
 
180
      if tag then
 
181
         output.buffer:apply_tag(tag, output.buffer:get_iter_at_offset(offset),
 
182
                                 end_iter)
 
183
      end
 
184
 
 
185
      -- Scroll so that the end of the buffer is visible, but only in
 
186
      -- case that cursor is at the very end of the view.  This avoids
 
187
      -- autoscroll when user tries to select something in the output
 
188
      -- view.
 
189
      local cursor = output.buffer:get_iter_at_mark(output.buffer:get_insert())
 
190
      if end_iter:get_offset() == cursor:get_offset() then
 
191
         output:scroll_mark_onscreen(output_end_mark)
 
192
      end
 
193
   end
 
194
 
 
195
   -- Define history buffer and operations with it.
 
196
   local history = { '', position = 1 }
 
197
   local function history_select(new_position)
 
198
      history[history.position] = entry.buffer.text
 
199
      history.position = new_position
 
200
      entry.buffer.text = history[history.position]
 
201
      entry.buffer:place_cursor(entry.buffer:get_end_iter())
 
202
      actions.up.sensitive = history.position > 1
 
203
      actions.down.sensitive = history.position < #history
 
204
   end
 
205
 
 
206
   -- History navigation actions.
 
207
   function actions.up:on_activate()
 
208
      history_select(history.position - 1)
 
209
   end
 
210
   function actions.down:on_activate()
 
211
      history_select(history.position + 1)
 
212
   end
 
213
 
 
214
   -- Execute Lua command from entry and log result into output.
 
215
   function actions.execute:on_activate()
 
216
      -- Get contents of the entry.
 
217
      local text = entry.buffer.text:gsub('^%s?(=)%s*', 'return ')
 
218
      if text == '' then return end
 
219
 
 
220
      -- Add command to the output view.
 
221
      append_output(text:gsub('\n*$', '\n', 1), 'command')
 
222
 
 
223
      -- Try to execute the command.
 
224
      local chunk, answer = (loadstring or load)(text, '=stdin')
 
225
      local tag = 'error'
 
226
      if not chunk then
 
227
         answer = answer:gsub('\n*$', '\n', 1)
 
228
      else
 
229
         (function(ok, ...)
 
230
             if not ok then
 
231
                answer = tostring(...):gsub('\n*$', '\n', 1)
 
232
             else
 
233
                -- Stringize the results.
 
234
                answer = {}
 
235
                for i = 1, select('#', ...) do
 
236
                   answer[#answer + 1] = tostring(select(i, ...))
 
237
                end
 
238
                answer = #answer > 0 and table.concat(answer, '\t') .. '\n'
 
239
                tag = 'result'
 
240
             end
 
241
          end)(pcall(chunk))
 
242
      end
 
243
 
 
244
      -- Add answer to the output pane.
 
245
      if answer then append_output(answer, tag) end
 
246
 
 
247
      if tag == 'error' then
 
248
         -- Try to parse the error and find line to place the cursor
 
249
         local line = answer:match('^stdin:(%d+):')
 
250
         if line then
 
251
            entry.buffer:place_cursor(entry.buffer:get_iter_at_line_offset(
 
252
                                         line - 1, 0))
 
253
         end
 
254
      else
 
255
         -- Store current text as the last item in the history, but
 
256
         -- avoid duplicating items.
 
257
         history[#history] = (history[#history - 1] ~= text) and text or nil
 
258
 
 
259
         -- Add new empty item to the history, point position to it.
 
260
         history.position = #history + 1
 
261
         history[history.position] = ''
 
262
 
 
263
         -- Enable/disable history navigation actions.
 
264
         actions.up.sensitive = history.position > 1
 
265
         actions.down.sensitive = false
 
266
 
 
267
         -- Clear contents of the entry buffer.
 
268
         entry.buffer.text = ''
 
269
      end
 
270
   end
 
271
 
 
272
   -- Intercept assorted keys in order to implement history
 
273
   -- navigation.  Ideally, this should be implemented using
 
274
   -- Gtk.BindingKey mechanism, but lgi still lacks possibility to
 
275
   -- derive classes and install new signals, which is needed in order
 
276
   -- to implement this.
 
277
   local keytable = {
 
278
      [Gdk.KEY_Return] = actions.execute,
 
279
      [Gdk.KEY_Up] = actions.up,
 
280
      [Gdk.KEY_Down] = actions.down,
 
281
   }
 
282
 
 
283
   function entry:on_key_press_event(event)
 
284
      -- Lookup action to be activated for specified key combination.
 
285
      local action = keytable[event.keyval]
 
286
      local state = event.state
 
287
      local without_control = not state.CONTROL_MASK 
 
288
      if not action or state.SHIFT_MASK
 
289
         or actions.multiline.active == without_control then
 
290
         return false
 
291
      end
 
292
 
 
293
      -- Ask textview whether it still wants to consume the key.
 
294
      if self:im_context_filter_keypress(event) then return true end
 
295
 
 
296
      -- Activate specified action.
 
297
      action:activate()
 
298
 
 
299
      -- Do not continue distributing the signal to the view.
 
300
      return true
 
301
   end
 
302
 
 
303
   function actions.about:on_activate()
 
304
      local about = Gtk.AboutDialog {
 
305
         program_name = 'LGI Lua Console',
 
306
         copyright = '(C) Copyright 2011 Pavel Holejšovský',
 
307
         authors = { 'Pavel Holejšovský' },
 
308
      }
 
309
      about.license_type = Gtk.License.MIT_X11
 
310
      about:run()
 
311
      about:hide()
 
312
   end
 
313
 
 
314
   function actions.clear:on_activate()
 
315
      output.buffer.text = ''
 
316
   end
 
317
 
 
318
   -- Return public object.
 
319
   return {
 
320
      widget = widget, output = output, entry = entry,
 
321
      actions = actions,
 
322
      append_output = append_output
 
323
   }
 
324
end
 
325
 
 
326
local old_print = print
 
327
local old_write = io.write
 
328
 
 
329
-- On activation, create and wire the whole widget hierarchy.
 
330
local app = Gtk.Application { application_id = 'org.lgi.gtkconsole' }
 
331
function app:on_activate()
 
332
   -- Create console.
 
333
   local console = Console()
 
334
 
 
335
   -- Create application window with console widget in it.
 
336
   console.window = Gtk.Window {
 
337
      application = app,
 
338
      title = "LGI Lua Console",
 
339
      default_width = 800, default_height = 600,
 
340
      console.widget
 
341
   }
 
342
 
 
343
   -- Make everything visible
 
344
   console.entry.has_focus = true
 
345
   console.window:show_all()
 
346
 
 
347
   -- Map quit action to window destroy.
 
348
   function console.actions.quit:on_activate()
 
349
      console.window:destroy()
 
350
   end
 
351
 
 
352
   -- Inject 'lgi' symbol into global namespace, for convenience.
 
353
   -- Also add 'console' table containing important elements of this
 
354
   -- console, so that it can be manipulated live.
 
355
   _G.lgi = lgi
 
356
   _G.console = console
 
357
 
 
358
   -- Override global 'print' and 'io.write' handlers, so that output
 
359
   -- goes to our output window (with special text style).
 
360
   function _G.print(...)
 
361
      local outs = {}
 
362
      for i = 1, select('#', ...) do
 
363
         outs[#outs + 1] = tostring(select(i, ...))
 
364
      end
 
365
      console.append_output(table.concat(outs, '\t') .. '\n', 'log')
 
366
   end
 
367
 
 
368
   function _G.io.write(...)
 
369
      for i = 1, select('#', ...) do
 
370
         console.append_output(select(i, ...), 'log')
 
371
      end
 
372
   end
 
373
end
 
374
 
 
375
-- Run the whole application
 
376
app:run { arg[0], ... }
 
377
 
 
378
-- Revert to old printing routines.
 
379
print = old_print
 
380
io.write = old_write