vimamsa 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.dockerignore +32 -0
- data/Dockerfile +45 -0
- data/README.md +2 -2
- data/custom_example.rb +38 -9
- data/docker_cmd.sh +7 -0
- data/exe/run_tests.rb +23 -0
- data/img/screenshot1.png +0 -0
- data/img/screenshot2.png +0 -0
- data/lib/vimamsa/actions.rb +8 -0
- data/lib/vimamsa/buffer.rb +165 -53
- data/lib/vimamsa/buffer_changetext.rb +68 -14
- data/lib/vimamsa/buffer_cursor.rb +9 -3
- data/lib/vimamsa/buffer_list.rb +14 -28
- data/lib/vimamsa/buffer_manager.rb +1 -1
- data/lib/vimamsa/conf.rb +33 -1
- data/lib/vimamsa/diff_buffer.rb +185 -0
- data/lib/vimamsa/editor.rb +149 -80
- data/lib/vimamsa/file_finder.rb +6 -2
- data/lib/vimamsa/gui.rb +330 -135
- data/lib/vimamsa/gui_dialog.rb +2 -0
- data/lib/vimamsa/gui_file_panel.rb +94 -0
- data/lib/vimamsa/gui_form_generator.rb +4 -2
- data/lib/vimamsa/gui_func_panel.rb +127 -0
- data/lib/vimamsa/gui_image.rb +2 -4
- data/lib/vimamsa/gui_menu.rb +54 -1
- data/lib/vimamsa/gui_select_window.rb +18 -6
- data/lib/vimamsa/gui_settings.rb +486 -0
- data/lib/vimamsa/gui_sourceview.rb +196 -8
- data/lib/vimamsa/gui_text.rb +0 -22
- data/lib/vimamsa/hyper_plain_text.rb +1 -0
- data/lib/vimamsa/key_actions.rb +54 -31
- data/lib/vimamsa/key_binding_tree.rb +154 -8
- data/lib/vimamsa/key_bindings_vimlike.rb +48 -35
- data/lib/vimamsa/langservp.rb +161 -7
- data/lib/vimamsa/macro.rb +54 -7
- data/lib/vimamsa/main.rb +1 -0
- data/lib/vimamsa/rbvma.rb +5 -0
- data/lib/vimamsa/string_util.rb +56 -0
- data/lib/vimamsa/test_framework.rb +137 -0
- data/lib/vimamsa/util.rb +3 -36
- data/lib/vimamsa/version.rb +1 -1
- data/modules/calculator/calculator.rb +318 -0
- data/modules/calculator/calculator_info.rb +3 -0
- data/modules/terminal/terminal.rb +140 -0
- data/modules/terminal/terminal_info.rb +3 -0
- data/run_tests.rb +89 -0
- data/styles/dark.xml +1 -1
- data/styles/molokai_edit.xml +2 -2
- data/tests/key_bindings.rb +2 -0
- data/tests/test_basic_editing.rb +86 -0
- data/tests/test_copy_paste.rb +88 -0
- data/tests/test_key_bindings.rb +152 -0
- data/tests/test_module_interface.rb +98 -0
- data/tests/test_undo.rb +201 -0
- data/vimamsa.gemspec +6 -5
- metadata +52 -14
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Scientific calculator widget embeddable inline in a text buffer.
|
|
2
|
+
#
|
|
3
|
+
# Enable this module in Preferences → Modules → "Scientific Calculator".
|
|
4
|
+
#
|
|
5
|
+
# Usage (once enabled):
|
|
6
|
+
# :insert_calculator action — inserts the widget at the cursor position
|
|
7
|
+
#
|
|
8
|
+
# The widget occupies a single U+FFFC (object replacement) character in the
|
|
9
|
+
# Ruby buffer, keeping GTK/Ruby buffer offsets aligned.
|
|
10
|
+
# Press ↩ inside the calculator to insert the current result after the widget.
|
|
11
|
+
|
|
12
|
+
class VmaCalculator < Gtk::Box
|
|
13
|
+
BUTTON_LAYOUT = [
|
|
14
|
+
# label row col w h
|
|
15
|
+
["sin", 0, 0, 1, 1],
|
|
16
|
+
["cos", 0, 1, 1, 1],
|
|
17
|
+
["tan", 0, 2, 1, 1],
|
|
18
|
+
["log", 0, 3, 1, 1],
|
|
19
|
+
["ln", 0, 4, 1, 1],
|
|
20
|
+
["√x", 1, 0, 1, 1],
|
|
21
|
+
["x²", 1, 1, 1, 1],
|
|
22
|
+
["xʸ", 1, 2, 1, 1],
|
|
23
|
+
["π", 1, 3, 1, 1],
|
|
24
|
+
["e", 1, 4, 1, 1],
|
|
25
|
+
["C", 2, 0, 1, 1],
|
|
26
|
+
["±", 2, 1, 1, 1],
|
|
27
|
+
["%", 2, 2, 1, 1],
|
|
28
|
+
["⌫", 2, 3, 1, 1],
|
|
29
|
+
["↩", 2, 4, 1, 1],
|
|
30
|
+
["7", 3, 0, 1, 1],
|
|
31
|
+
["8", 3, 1, 1, 1],
|
|
32
|
+
["9", 3, 2, 1, 1],
|
|
33
|
+
["÷", 3, 3, 1, 1],
|
|
34
|
+
["(", 3, 4, 1, 1],
|
|
35
|
+
["4", 4, 0, 1, 1],
|
|
36
|
+
["5", 4, 1, 1, 1],
|
|
37
|
+
["6", 4, 2, 1, 1],
|
|
38
|
+
["×", 4, 3, 1, 1],
|
|
39
|
+
[")", 4, 4, 1, 1],
|
|
40
|
+
["1", 5, 0, 1, 1],
|
|
41
|
+
["2", 5, 1, 1, 1],
|
|
42
|
+
["3", 5, 2, 1, 1],
|
|
43
|
+
["-", 5, 3, 1, 1],
|
|
44
|
+
["=", 5, 4, 2, 1],
|
|
45
|
+
["0", 6, 0, 2, 1],
|
|
46
|
+
[".", 6, 2, 1, 1],
|
|
47
|
+
["+", 6, 3, 1, 1],
|
|
48
|
+
].freeze
|
|
49
|
+
|
|
50
|
+
def initialize(anchor_pos, bufo)
|
|
51
|
+
super(:vertical, 2)
|
|
52
|
+
@anchor_pos = anchor_pos
|
|
53
|
+
@bufo = bufo
|
|
54
|
+
@expression = ""
|
|
55
|
+
@last_result = nil
|
|
56
|
+
@variables = {} # name => value, updated on every assignment
|
|
57
|
+
# Persistent binding for eval so that assigned variables (e.g. x = 5)
|
|
58
|
+
# survive between calculations. Scoped to self so deg2rad and Math are accessible.
|
|
59
|
+
@eval_binding = instance_eval { binding }
|
|
60
|
+
|
|
61
|
+
self.margin_top = 4
|
|
62
|
+
self.margin_bottom = 4
|
|
63
|
+
self.margin_start = 4
|
|
64
|
+
self.margin_end = 4
|
|
65
|
+
|
|
66
|
+
setup_ui
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def setup_ui
|
|
72
|
+
@expr_label = Gtk::Label.new("")
|
|
73
|
+
@expr_label.xalign = 1.0
|
|
74
|
+
@expr_label.add_css_class("dim-label")
|
|
75
|
+
append(@expr_label)
|
|
76
|
+
|
|
77
|
+
@display = Gtk::Entry.new
|
|
78
|
+
@display.editable = true
|
|
79
|
+
@display.xalign = 1.0
|
|
80
|
+
@display.text = "0"
|
|
81
|
+
@display.add_css_class("monospace")
|
|
82
|
+
@display.width_chars = 20
|
|
83
|
+
append(@display)
|
|
84
|
+
|
|
85
|
+
# Variable assignment row: [→ var [__name__] [Assign]]
|
|
86
|
+
var_row = Gtk::Box.new(:horizontal, 4)
|
|
87
|
+
var_row.margin_top = 4
|
|
88
|
+
|
|
89
|
+
var_arrow = Gtk::Label.new("→ var")
|
|
90
|
+
var_arrow.add_css_class("dim-label")
|
|
91
|
+
|
|
92
|
+
@var_name_entry = Gtk::Entry.new
|
|
93
|
+
@var_name_entry.placeholder_text = "name"
|
|
94
|
+
@var_name_entry.width_chars = 8
|
|
95
|
+
@var_name_entry.hexpand = true
|
|
96
|
+
track_entry_focus(@var_name_entry)
|
|
97
|
+
track_entry_focus(@display)
|
|
98
|
+
|
|
99
|
+
assign_btn = Gtk::Button.new(label: "Assign")
|
|
100
|
+
assign_btn.signal_connect("clicked") { assign_variable }
|
|
101
|
+
|
|
102
|
+
var_row.append(var_arrow)
|
|
103
|
+
var_row.append(@var_name_entry)
|
|
104
|
+
var_row.append(assign_btn)
|
|
105
|
+
append(var_row)
|
|
106
|
+
|
|
107
|
+
# Flow of variable buttons; clicking one appends the name to the expression.
|
|
108
|
+
@var_buttons_box = Gtk::FlowBox.new
|
|
109
|
+
@var_buttons_box.selection_mode = :none
|
|
110
|
+
@var_buttons_box.column_spacing = 2
|
|
111
|
+
@var_buttons_box.row_spacing = 2
|
|
112
|
+
@var_buttons_box.margin_top = 2
|
|
113
|
+
@var_buttons_box.homogeneous = false
|
|
114
|
+
append(@var_buttons_box)
|
|
115
|
+
|
|
116
|
+
grid = Gtk::Grid.new
|
|
117
|
+
grid.row_spacing = 2
|
|
118
|
+
grid.column_spacing = 2
|
|
119
|
+
grid.margin_top = 4
|
|
120
|
+
append(grid)
|
|
121
|
+
|
|
122
|
+
BUTTON_LAYOUT.each do |label, row, col, w, h|
|
|
123
|
+
btn = Gtk::Button.new(label: label)
|
|
124
|
+
btn.width_request = 42
|
|
125
|
+
btn.height_request = 36
|
|
126
|
+
btn.signal_connect("clicked") { on_button(label) }
|
|
127
|
+
btn.add_css_class("suggested-action") if %w[= ↩].include?(label)
|
|
128
|
+
btn.add_css_class("destructive-action") if %w[C ⌫].include?(label)
|
|
129
|
+
grid.attach(btn, col, row, w, h)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def on_button(label)
|
|
134
|
+
case label
|
|
135
|
+
when "C"
|
|
136
|
+
@expression = ""
|
|
137
|
+
show_display("0")
|
|
138
|
+
when "⌫"
|
|
139
|
+
@expression = @expression[0..-2] || ""
|
|
140
|
+
show_display(@expression.empty? ? "0" : @expression)
|
|
141
|
+
when "=" then evaluate
|
|
142
|
+
when "↩" then insert_result
|
|
143
|
+
when "sin" then apply_unary("Math.sin(deg2rad(%s))")
|
|
144
|
+
when "cos" then apply_unary("Math.cos(deg2rad(%s))")
|
|
145
|
+
when "tan" then apply_unary("Math.tan(deg2rad(%s))")
|
|
146
|
+
when "log" then apply_unary("Math.log10(%s)")
|
|
147
|
+
when "ln" then apply_unary("Math.log(%s)")
|
|
148
|
+
when "√x" then apply_unary("Math.sqrt(%s)")
|
|
149
|
+
when "x²"
|
|
150
|
+
@expression = "(#{@expression})**2"
|
|
151
|
+
evaluate
|
|
152
|
+
when "xʸ"
|
|
153
|
+
@expression += "**"
|
|
154
|
+
show_display(@expression)
|
|
155
|
+
when "π"
|
|
156
|
+
@expression += Math::PI.to_s
|
|
157
|
+
show_display(@expression)
|
|
158
|
+
when "e"
|
|
159
|
+
@expression += Math::E.to_s
|
|
160
|
+
show_display(@expression)
|
|
161
|
+
when "±"
|
|
162
|
+
@expression = @expression.start_with?("-") ? @expression[1..] : "-#{@expression}"
|
|
163
|
+
show_display(@expression.empty? ? "0" : @expression)
|
|
164
|
+
when "%"
|
|
165
|
+
@expression = "(#{@expression})/100.0"
|
|
166
|
+
evaluate
|
|
167
|
+
when "÷"
|
|
168
|
+
@expression += "/"
|
|
169
|
+
show_display(@expression)
|
|
170
|
+
when "×"
|
|
171
|
+
@expression += "*"
|
|
172
|
+
show_display(@expression)
|
|
173
|
+
else
|
|
174
|
+
@expression += label
|
|
175
|
+
show_display(@expression)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def apply_unary(template)
|
|
180
|
+
@expression = template % "(#{@expression}).to_f"
|
|
181
|
+
evaluate
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def evaluate
|
|
185
|
+
# Pick up any text the user typed directly into the display field.
|
|
186
|
+
@expression = @display.text
|
|
187
|
+
expr = @expression
|
|
188
|
+
# Coerce bare integer literals to floats so that expressions like (1/6)**3
|
|
189
|
+
# use floating-point division instead of integer division.
|
|
190
|
+
# Lookbehind (?<![.\d]) and lookahead (?![.\d]) ensure digits that are
|
|
191
|
+
# already part of a float literal (e.g. the 53 in 1.53) are left alone.
|
|
192
|
+
eval_expr = expr.gsub(/(?<![.\d])(\d+)(?![.\d])/, '\1.0')
|
|
193
|
+
result = eval(eval_expr, @eval_binding)
|
|
194
|
+
@last_result = result.is_a?(Numeric) ? result.to_f : nil
|
|
195
|
+
formatted = result.is_a?(Float) && result == result.floor && result.abs < 1e15 ?
|
|
196
|
+
result.to_i.to_s : result.round(5).to_s
|
|
197
|
+
@expr_label.text = expr
|
|
198
|
+
@expression = formatted
|
|
199
|
+
show_display(formatted)
|
|
200
|
+
append_trail("#{expr} = #{formatted}")
|
|
201
|
+
# If the expression was a typed assignment (e.g. "x = 5"), record the variable.
|
|
202
|
+
if (m = expr.match(/\A\s*([a-zA-Z_]\w*)\s*=(?!=)/)) && @last_result
|
|
203
|
+
record_variable(m[1], @last_result)
|
|
204
|
+
end
|
|
205
|
+
rescue => e
|
|
206
|
+
@expr_label.text = @expression
|
|
207
|
+
show_display("Error")
|
|
208
|
+
@expression = ""
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Assign the current display value to the variable name typed in @var_name_entry.
|
|
212
|
+
# Attach a focus controller to an entry so the vimamsa key handler steps
|
|
213
|
+
# aside while the entry is active and resumes when it loses focus.
|
|
214
|
+
def track_entry_focus(entry)
|
|
215
|
+
fc = Gtk::EventControllerFocus.new
|
|
216
|
+
entry.add_controller(fc)
|
|
217
|
+
fc.signal_connect("enter") { vma.gui.notify_entry_focus(true) }
|
|
218
|
+
fc.signal_connect("leave") { vma.gui.notify_entry_focus(false) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def assign_variable
|
|
222
|
+
name = @var_name_entry.text.strip
|
|
223
|
+
return if name.empty? || @last_result.nil?
|
|
224
|
+
return unless name.match?(/\A[a-zA-Z_]\w*\z/) # must be a valid Ruby identifier
|
|
225
|
+
eval("#{name} = #{@last_result}", @eval_binding)
|
|
226
|
+
record_variable(name, @last_result)
|
|
227
|
+
append_trail("#{name} = #{@last_result}")
|
|
228
|
+
@expr_label.text = "#{name} assigned"
|
|
229
|
+
rescue => e
|
|
230
|
+
@expr_label.text = "Error"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Store a variable name/value and refresh the variable list widget.
|
|
234
|
+
def record_variable(name, value)
|
|
235
|
+
@variables[name] = value
|
|
236
|
+
rebuild_var_list
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Rebuild variable buttons from @variables — one compact button per name.
|
|
240
|
+
def rebuild_var_list
|
|
241
|
+
while (child = @var_buttons_box.first_child)
|
|
242
|
+
@var_buttons_box.remove(child)
|
|
243
|
+
end
|
|
244
|
+
@variables.each_key do |name|
|
|
245
|
+
btn = Gtk::Button.new(label: name)
|
|
246
|
+
btn.signal_connect("clicked") do
|
|
247
|
+
@expression += name
|
|
248
|
+
show_display(@expression)
|
|
249
|
+
end
|
|
250
|
+
@var_buttons_box.append(btn)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Insert one calculation line into the buffer just above the widget.
|
|
255
|
+
# Each insertion shifts the widget down, so @anchor_pos is updated to follow it.
|
|
256
|
+
def append_trail(text)
|
|
257
|
+
line = "#{text}\n"
|
|
258
|
+
@bufo.insert_txt_at(line, @anchor_pos)
|
|
259
|
+
@anchor_pos += line.length
|
|
260
|
+
# Flush Ruby buffer deltas to the GTK view so the text appears immediately.
|
|
261
|
+
@bufo.view.handle_deltas
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def show_display(text)
|
|
265
|
+
@display.text = text
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Insert the current result into the buffer immediately after the widget.
|
|
269
|
+
def insert_result
|
|
270
|
+
return if @last_result.nil?
|
|
271
|
+
result_str = (@last_result == @last_result.floor && @last_result.abs < 1e15) ?
|
|
272
|
+
@last_result.to_i.to_s : @last_result.to_s
|
|
273
|
+
@bufo.insert_txt_at(result_str, @anchor_pos + 1)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def deg2rad(deg)
|
|
277
|
+
deg * Math::PI / 180.0
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Insert a VmaCalculator widget at the current cursor position.
|
|
282
|
+
# U+FFFC is inserted in the Ruby buffer as a placeholder so that
|
|
283
|
+
# Ruby and GTK buffer offsets stay aligned (both have exactly one
|
|
284
|
+
# character at the anchor position).
|
|
285
|
+
def insert_calculator_in_buffer
|
|
286
|
+
view = vma.gui.view
|
|
287
|
+
bufo = vma.buf
|
|
288
|
+
return if view.nil? || bufo.nil?
|
|
289
|
+
|
|
290
|
+
pos = bufo.pos
|
|
291
|
+
|
|
292
|
+
# Step 1: insert the placeholder in the Ruby buffer and flush it to GTK.
|
|
293
|
+
bufo.insert_txt_at("\uFFFC", pos)
|
|
294
|
+
view.handle_deltas
|
|
295
|
+
|
|
296
|
+
# Step 2: the GTK buffer now has a plain U+FFFC at pos.
|
|
297
|
+
# Replace it with a real child anchor, then attach the widget.
|
|
298
|
+
iter = view.buffer.get_iter_at(:offset => pos)
|
|
299
|
+
if iter && iter.char == "\uFFFC"
|
|
300
|
+
iter2 = view.buffer.get_iter_at(:offset => pos + 1)
|
|
301
|
+
view.buffer.delete(iter, iter2)
|
|
302
|
+
iter = view.buffer.get_iter_at(:offset => pos)
|
|
303
|
+
anchor = view.buffer.create_child_anchor(iter)
|
|
304
|
+
calc = VmaCalculator.new(pos, bufo)
|
|
305
|
+
view.add_child_at_anchor(calc, anchor)
|
|
306
|
+
calc.show
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def calculator_init
|
|
311
|
+
reg_act(:insert_calculator, proc { insert_calculator_in_buffer }, "Insert scientific calculator widget at cursor")
|
|
312
|
+
vma.gui.menu.add_module_action(:insert_calculator, "Insert Calculator")
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def calculator_disable
|
|
316
|
+
unreg_act(:insert_calculator)
|
|
317
|
+
vma.gui.menu.remove_module_action(:insert_calculator)
|
|
318
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
class VmaTerminal < Gtk::Box
|
|
2
|
+
def initialize(anchor_pos, bufo)
|
|
3
|
+
super(:vertical, 0)
|
|
4
|
+
@anchor_pos = anchor_pos
|
|
5
|
+
@bufo = bufo
|
|
6
|
+
|
|
7
|
+
@term = Vte::Terminal.new
|
|
8
|
+
@term.hexpand = true
|
|
9
|
+
@term.vexpand = true
|
|
10
|
+
@term.set_size_request(800, 400)
|
|
11
|
+
@term.focusable = true
|
|
12
|
+
|
|
13
|
+
@term.signal_connect("realize") do
|
|
14
|
+
view_width = @bufo.view.allocated_width
|
|
15
|
+
w = view_width > 50 ? view_width - 50 : view_width
|
|
16
|
+
@term.set_size_request(w, 400)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
click = Gtk::GestureClick.new
|
|
20
|
+
click.signal_connect("pressed") do
|
|
21
|
+
vma.gui.instance_variable_set(:@kbd_passthrough, true)
|
|
22
|
+
@term.grab_focus
|
|
23
|
+
end
|
|
24
|
+
@term.add_controller(click)
|
|
25
|
+
|
|
26
|
+
append(@term)
|
|
27
|
+
|
|
28
|
+
bar = Gtk::Box.new(:horizontal, 4)
|
|
29
|
+
bar.margin_start = 4
|
|
30
|
+
btn = Gtk::Button.new(label: "Copy to buffer")
|
|
31
|
+
btn.signal_connect("clicked") { copy_to_buffer }
|
|
32
|
+
bar.append(btn)
|
|
33
|
+
append(bar)
|
|
34
|
+
|
|
35
|
+
spawn_shell
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def spawn_shell
|
|
41
|
+
# @term.spawn_async(
|
|
42
|
+
# Vte::PtyFlags::DEFAULT,
|
|
43
|
+
# nil,
|
|
44
|
+
# [ENV["SHELL"] || "/bin/bash"],
|
|
45
|
+
# GLib::Spawn::DEFAULT,
|
|
46
|
+
# # -1
|
|
47
|
+
# )
|
|
48
|
+
|
|
49
|
+
# @term.spawn_async(
|
|
50
|
+
# Vte::PtyFlags::DEFAULT,
|
|
51
|
+
# # [ENV["SHELL"] || "/bin/bash"],
|
|
52
|
+
# "/bin/bash",
|
|
53
|
+
# [],
|
|
54
|
+
# [],
|
|
55
|
+
@term.spawn
|
|
56
|
+
# @term.spawn_async(
|
|
57
|
+
# "/home/sjs/",
|
|
58
|
+
# [],
|
|
59
|
+
# [],
|
|
60
|
+
# GLib::Spawn::DEFAULT,
|
|
61
|
+
# )
|
|
62
|
+
# [ENV["SHELL"] || "/bin/bash"],
|
|
63
|
+
# "/bin/bash",
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# g_spawn_async (
|
|
67
|
+
# const gchar* working_directory,
|
|
68
|
+
# gchar** argv,
|
|
69
|
+
# gchar** envp,
|
|
70
|
+
# GSpawnFlags flags,
|
|
71
|
+
# GSpawnChildSetupFunc child_setup,
|
|
72
|
+
# gpointer user_data,
|
|
73
|
+
# GPid* child_pid,
|
|
74
|
+
# GError** error
|
|
75
|
+
# )
|
|
76
|
+
|
|
77
|
+
# GLib::Spawn::DEFAULT,
|
|
78
|
+
# -1
|
|
79
|
+
# )
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def copy_to_buffer
|
|
84
|
+
cols = @term.column_count
|
|
85
|
+
adj = @term.vadjustment
|
|
86
|
+
visible_rows = adj.page_size.to_i
|
|
87
|
+
first_row = adj.upper.to_i - visible_rows
|
|
88
|
+
last_row = adj.upper.to_i
|
|
89
|
+
text = @term.get_text_range_format(:text, first_row, 0, last_row, cols - 1)
|
|
90
|
+
.first
|
|
91
|
+
.gsub(/\s+$/, "")
|
|
92
|
+
.rstrip
|
|
93
|
+
return if text.empty?
|
|
94
|
+
|
|
95
|
+
insert_pos = @anchor_pos + 1
|
|
96
|
+
@bufo.insert_txt_at("\n" + text + "\n", insert_pos)
|
|
97
|
+
@bufo.view.handle_deltas
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def insert_terminal_in_buffer
|
|
102
|
+
view = vma.gui.view
|
|
103
|
+
bufo = vma.buf
|
|
104
|
+
return if view.nil? || bufo.nil?
|
|
105
|
+
|
|
106
|
+
pos = bufo.pos
|
|
107
|
+
|
|
108
|
+
bufo.insert_txt_at("\uFFFC", pos)
|
|
109
|
+
view.handle_deltas
|
|
110
|
+
|
|
111
|
+
iter = view.buffer.get_iter_at(:offset => pos)
|
|
112
|
+
if iter && iter.char == "\uFFFC"
|
|
113
|
+
iter2 = view.buffer.get_iter_at(:offset => pos + 1)
|
|
114
|
+
view.buffer.delete(iter, iter2)
|
|
115
|
+
iter = view.buffer.get_iter_at(:offset => pos)
|
|
116
|
+
anchor = view.buffer.create_child_anchor(iter)
|
|
117
|
+
widget = VmaTerminal.new(pos, bufo)
|
|
118
|
+
|
|
119
|
+
char_w = view.pango_context.get_metrics(nil, nil).approximate_char_width / Pango::SCALE
|
|
120
|
+
columns = char_w > 0 ? (view.allocated_width / char_w) - 4 : 80
|
|
121
|
+
# require "pry";binding.pry
|
|
122
|
+
# widget.instance_variable_get(:@term).set_columns(columns)
|
|
123
|
+
widget.instance_variable_get(:@term).set_size(columns,20)
|
|
124
|
+
|
|
125
|
+
view.add_child_at_anchor(widget, anchor)
|
|
126
|
+
widget.show
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def terminal_init
|
|
131
|
+
require "vte4"
|
|
132
|
+
reg_act(:insert_terminal, proc { insert_terminal_in_buffer }, "Insert embedded terminal at cursor")
|
|
133
|
+
add_keys "terminal", { "C , t" => :insert_terminal }
|
|
134
|
+
vma.gui.menu.add_module_action(:insert_terminal, "Insert Terminal")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def terminal_disable
|
|
138
|
+
unreg_act(:insert_terminal)
|
|
139
|
+
vma.gui.menu.remove_module_action(:insert_terminal)
|
|
140
|
+
end
|
data/run_tests.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$stdout.sync = true
|
|
3
|
+
$stderr.sync = true
|
|
4
|
+
# run_tests.rb — Discover and run all vimamsa tests, write a report.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ruby run_tests.rb [REPORT_FILE] default report: test_report.txt
|
|
8
|
+
#
|
|
9
|
+
# For headless / CI use:
|
|
10
|
+
# xvfb-run ruby run_tests.rb
|
|
11
|
+
|
|
12
|
+
require "open3"
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR = File.expand_path("..", __FILE__)
|
|
15
|
+
REPORT = File.expand_path(ARGV.first || "test_report.txt", SCRIPT_DIR)
|
|
16
|
+
|
|
17
|
+
# ── Discover test files ───────────────────────────────────────────────────────
|
|
18
|
+
test_files = Dir.glob(File.join(SCRIPT_DIR, "tests", "test_*.rb")).sort
|
|
19
|
+
|
|
20
|
+
if test_files.empty?
|
|
21
|
+
warn "ERROR: No test files found in tests/"
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# ── Guard: need a display ─────────────────────────────────────────────────────
|
|
26
|
+
unless ENV["DISPLAY"] || ENV["WAYLAND_DISPLAY"]
|
|
27
|
+
xvfb = `which xvfb-run 2>/dev/null`.strip
|
|
28
|
+
if xvfb.empty?
|
|
29
|
+
warn "ERROR: No display found (DISPLAY/WAYLAND_DISPLAY unset) and xvfb-run not available."
|
|
30
|
+
warn " Install xvfb or run from a desktop session."
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
puts "No display detected — re-running under xvfb-run."
|
|
34
|
+
exec("xvfb-run", "-a", RbConfig.ruby, __FILE__, *ARGV)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# ── Print header ──────────────────────────────────────────────────────────────
|
|
38
|
+
puts "=== Vimamsa test runner ==="
|
|
39
|
+
puts "Running #{test_files.size} test file(s):"
|
|
40
|
+
test_files.each { |f| puts " #{File.basename(f)}" }
|
|
41
|
+
puts ""
|
|
42
|
+
|
|
43
|
+
# ── Run tests ─────────────────────────────────────────────────────────────────
|
|
44
|
+
# All files loaded in a single vimamsa session (one GTK startup).
|
|
45
|
+
# stdout → streamed live and captured for the report
|
|
46
|
+
# stderr → suppressed (GTK/GLib noise)
|
|
47
|
+
cmd = [RbConfig.ruby, "exe/run_tests.rb", "--test", *test_files]
|
|
48
|
+
raw = ""
|
|
49
|
+
IO.popen([*cmd, err: File::NULL], chdir: SCRIPT_DIR) do |io|
|
|
50
|
+
io.each_line do |line|
|
|
51
|
+
print line
|
|
52
|
+
raw << line
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
puts ""
|
|
56
|
+
|
|
57
|
+
# ── Parse summary ─────────────────────────────────────────────────────────────
|
|
58
|
+
summary_line = raw.lines.grep(/^Results:/).last.to_s
|
|
59
|
+
passed = summary_line[/(\d+) passed/, 1].to_i
|
|
60
|
+
failed = summary_line[/(\d+) failed/, 1].to_i
|
|
61
|
+
errors = raw.lines.count { |l| l.match?(/^\s+ERROR\s/) }
|
|
62
|
+
|
|
63
|
+
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
|
64
|
+
|
|
65
|
+
# ── Write report ──────────────────────────────────────────────────────────────
|
|
66
|
+
File.open(REPORT, "w") do |f|
|
|
67
|
+
f.puts "Vimamsa Test Report — #{timestamp}"
|
|
68
|
+
f.puts "=" * 52
|
|
69
|
+
f.puts ""
|
|
70
|
+
f.puts "Test files (#{test_files.size}):"
|
|
71
|
+
test_files.each { |tf| f.puts " #{File.basename(tf)}" }
|
|
72
|
+
f.puts ""
|
|
73
|
+
f.puts "-" * 52
|
|
74
|
+
f.puts raw
|
|
75
|
+
f.puts "-" * 52
|
|
76
|
+
f.puts ""
|
|
77
|
+
f.puts "Summary"
|
|
78
|
+
f.puts "-" * 52
|
|
79
|
+
f.puts " Passed : #{passed}"
|
|
80
|
+
f.puts " Failed : #{failed}"
|
|
81
|
+
f.puts " Errors : #{errors}"
|
|
82
|
+
f.puts "\n ALL TESTS PASSED" if failed + errors == 0
|
|
83
|
+
f.puts ""
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
puts "Report written to: #{REPORT}"
|
|
87
|
+
puts " Passed: #{passed} Failed: #{failed} Errors: #{errors}"
|
|
88
|
+
|
|
89
|
+
exit(failed + errors == 0 ? 0 : 1)
|
data/styles/dark.xml
CHANGED
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
|
|
129
129
|
<style name="def:title" scale="2.0" bold="true" />
|
|
130
130
|
<style name="def:hyperlink" foreground="lightblue" bold="true" />
|
|
131
|
-
<style name="def:heading0" scale="
|
|
131
|
+
<style name="def:heading0" scale="2.0" bold="true" foreground="heading" />
|
|
132
132
|
<style name="def:heading1" scale="1.75" bold="true" foreground="heading" />
|
|
133
133
|
<style name="def:heading2" scale="1.5" bold="true" foreground="heading" />
|
|
134
134
|
<style name="def:heading3" scale="1.25" bold="true" foreground="heading" />
|
data/styles/molokai_edit.xml
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<style foreground="#66D9EF" name="def:type"/>
|
|
32
32
|
<!--style background="#272822" foreground="#F8F8F2" name="text"/-->
|
|
33
33
|
<style background="#000000" foreground="#F8F8F2" name="text"/>
|
|
34
|
-
<style foreground="#
|
|
34
|
+
<style foreground="#b0b0b0" name="def:comment"/>
|
|
35
35
|
<style background="#3E3D32" name="current-line"/>
|
|
36
36
|
<style background="#222222" foreground="#F8F8F2" name="text"/>
|
|
37
37
|
<!--style foreground="#465457" name="def:comment"/-->
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
<style name="def:title" scale="2.0" bold="true" />
|
|
42
42
|
<style name="def:hyperlink" foreground="lightblue" bold="true" />
|
|
43
|
-
<style name="def:heading0" scale="
|
|
43
|
+
<style name="def:heading0" scale="2.0" bold="true" foreground="heading" />
|
|
44
44
|
<style name="def:heading1" scale="1.75" bold="true" foreground="heading" />
|
|
45
45
|
<style name="def:heading2" scale="1.5" bold="true" foreground="heading" />
|
|
46
46
|
<style name="def:heading3" scale="1.25" bold="true" foreground="heading" />
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
class TestBasicEditing < VmaTest
|
|
2
|
+
|
|
3
|
+
def test_insert_text
|
|
4
|
+
act 'buf.insert_txt("hello")'
|
|
5
|
+
assert_buf "hello\n"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_delete_char_forward
|
|
9
|
+
act 'buf.insert_txt("abcde")'
|
|
10
|
+
act :jump_to_start_of_buffer
|
|
11
|
+
act :delete_char_forward
|
|
12
|
+
act :delete_char_forward
|
|
13
|
+
assert_buf "cde\n"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_delete_backward
|
|
17
|
+
act 'buf.insert_txt("abcde")'
|
|
18
|
+
act "buf.delete(BACKWARD_CHAR)"
|
|
19
|
+
act "buf.delete(BACKWARD_CHAR)"
|
|
20
|
+
assert_buf "abc\n"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_undo_redo
|
|
24
|
+
act 'buf.insert_txt("hello")'
|
|
25
|
+
assert_buf "hello\n"
|
|
26
|
+
act :undo
|
|
27
|
+
assert_buf "\n"
|
|
28
|
+
act :redo
|
|
29
|
+
assert_buf "hello\n"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_new_line
|
|
33
|
+
act 'buf.insert_txt("first")'
|
|
34
|
+
act 'buf.insert_txt("\n")'
|
|
35
|
+
act 'buf.insert_txt("second")'
|
|
36
|
+
assert_buf "first\nsecond\n"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_jump_start_end
|
|
40
|
+
act 'buf.insert_txt("hello")'
|
|
41
|
+
act :jump_to_start_of_buffer
|
|
42
|
+
assert_pos 0, 0
|
|
43
|
+
act :jump_to_end_of_buffer
|
|
44
|
+
assert_pos 0, 5
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_copy_paste
|
|
48
|
+
#Note: Buffer contains a single "\n" by default
|
|
49
|
+
act 'buf.insert_txt("hello\n")'
|
|
50
|
+
act :jump_to_start_of_buffer
|
|
51
|
+
act :copy_cur_line
|
|
52
|
+
act :jump_to_end_of_buffer
|
|
53
|
+
act :paste_after_cursor
|
|
54
|
+
assert_buf "hello\n\nhello\n"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_delete_line
|
|
58
|
+
act 'buf.insert_txt("first\n")'
|
|
59
|
+
act 'buf.insert_txt("second")'
|
|
60
|
+
act :jump_to_start_of_buffer
|
|
61
|
+
act :delete_line
|
|
62
|
+
assert_buf "second\n"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class TestKeySequences < VmaTest
|
|
68
|
+
|
|
69
|
+
def test_insert_mode_via_keys
|
|
70
|
+
# 'i' enters insert mode in command mode
|
|
71
|
+
keys "i"
|
|
72
|
+
assert_mode :insert
|
|
73
|
+
keys "esc"
|
|
74
|
+
assert_mode :command
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_type_and_escape
|
|
78
|
+
keys "i"
|
|
79
|
+
# Type individual characters
|
|
80
|
+
"hello".each_char { |c| keys c }
|
|
81
|
+
keys "esc"
|
|
82
|
+
assert_buf "hello\n"
|
|
83
|
+
assert_mode :command
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|