glimmer-cs-gladiator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: af29d980faf6b1905a69f7a06bed1646fda6ebd1fd30f5016d5ca142afbefe98
4
+ data.tar.gz: a02ca4bb732b16bc26fb7c7f5d3c7644a7bb2a40ddd77458c96a4d1ecf0d0a87
5
+ SHA512:
6
+ metadata.gz: 962c8732ca907482bc9031f48974154a945d3276c3a7d7d2e10ee65899343070938bb3d140c7e03f8b2f13126a580ed4129c75503700336f58dffa0b3e17139a
7
+ data.tar.gz: 5e8e21ebf4e37d4ae244a92372f3d9d17dd8cbd4ee5ebbeedf3e8581493f5101d052e753764ed94c8a9119c6f0f72d9a453c7e168b22de919c31cc27ee3c4824
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2020 Andy Maleh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,110 @@
1
+ # Gladiator (Glimmer Editor) 0.1.0 - Glimmer Custom Shell
2
+
3
+ ![Gladiator](images/glimmer-gladiator.png)
4
+
5
+ Gladiator (short for Glimmer Editor) is a Glimmer sample project under on-going development.
6
+ It is not intended to be a full-fledged editor by any means, yet mostly a fun educational exercise in using Glimmer to build a text editor.
7
+ Gladiator is also a personal tool for shaping an editor exactly the way I like.
8
+ I leave building truly professional text editors to software tooling experts who would hopefully use Glimmer one day.
9
+
10
+ Gladiator currently supports the following text editing features:
11
+ - File explorer navigation to open file
12
+ - File lookup by name
13
+ - Find & Replace
14
+ - Show Line Numbers
15
+ - Jump to Line
16
+ - Remember last opened file, caret position, and top line
17
+ - Autosave on focus out/quit/open new file
18
+ - Watch open file for external changes to reflect in editor
19
+ - Duplicate Line(s)
20
+ - Kill Line(s)
21
+ - Move up one line
22
+ - Move down one line
23
+
24
+ ## Pre-requisites
25
+
26
+ - JRuby 9.2.11.1 (supporting Ruby 2.5.x syntax) (find at https://www.jruby.org/download)
27
+ - Java SE Runtime Environment 7 or higher (find at https://www.oracle.com/java/technologies/javase-downloads.html)
28
+
29
+ ## Setup Instructions
30
+
31
+ Install Gladiator gem by running (`jgem`, `jruby -S gem`, or `gem` directly if you have RVM):
32
+
33
+ ```
34
+ jgem install glimmer-cs-gladiator
35
+ ```
36
+
37
+ Afterwards, you may run `gladiator` to bring up the text editor:
38
+
39
+ ```
40
+ gladiator
41
+ ```
42
+
43
+ Note: If you cloned this project and bundle installed, you may invoke via `bin/gladiator` instead.
44
+
45
+ ### Glimmer Custom Shell Reuse
46
+
47
+ To reuse Gladiator as a Glimmer Custom Shell inside another Glimmer application, add the
48
+ following to the application's `Gemfile`:
49
+
50
+ ```
51
+ gem 'glimmer-cs-gladiator', '0.1.0'
52
+ ```
53
+
54
+ Run:
55
+
56
+ ```
57
+ jruby -S bundle
58
+ ```
59
+
60
+ And, then instantiate the Gladiator custom shell in your Glimmer application via the `gladiator` keyword.
61
+
62
+ ## Env Var Options
63
+
64
+ Gladiator opens with the current directory as the root by default.
65
+ If you would like to open another directory, set `LOCAL_DIR` environment variable.
66
+
67
+ Example:
68
+
69
+ ```
70
+ LOCAL_DIR="/Users/User/code" gladiator
71
+ ```
72
+
73
+ Opens Gladiator with "/Users/User/code" as the root directory.
74
+
75
+ ## Configuration
76
+
77
+ Gladiator automatically saves configuration data in a `.gladiator` file at the directory it is run from.
78
+
79
+ It currently remembers:
80
+ - Last opened file
81
+ - Caret position
82
+ - Top line position
83
+
84
+ ## Gotcha
85
+
86
+ Gladiator repetitively displays a signaling error that is harmless in practice:
87
+ ```
88
+ The signal HUP is in use by the JVM and will not work correctly on this platform
89
+ The signal INT is in use by the JVM and will not work correctly on this platform
90
+ The signal TERM is in use by the JVM and will not work correctly on this platform
91
+ ```
92
+
93
+ ## TODO
94
+
95
+ [TODO.md](TODO.md)
96
+
97
+ ## Contributing to glimmer-cs-gladiator
98
+
99
+ - Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
100
+ - Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
101
+ - Fork the project.
102
+ - Start a feature/bugfix branch.
103
+ - Commit and push until you are happy with your contribution.
104
+ - Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
105
+ - Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
106
+
107
+ ## Copyright
108
+
109
+ Copyright (c) 2020 Andy Maleh. See LICENSE.txt for
110
+ further details.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'glimmer/launcher'
4
+
5
+ gladiator_runner = File.expand_path('../gladiator_runner.rb', __FILE__)
6
+ Glimmer::Launcher.new([gladiator_runner, '-J-Xrs'] + ARGV).launch
@@ -0,0 +1,5 @@
1
+ require_relative '../lib/glimmer-cs-gladiator'
2
+
3
+ include Glimmer
4
+
5
+ gladiator.open
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH.unshift(File.expand_path('..', __FILE__))
2
+
3
+ require 'filewatcher'
4
+ require 'clipboard'
5
+ require 'views/glimmer/gladiator'
@@ -0,0 +1,116 @@
1
+ require 'models/glimmer/gladiator/file'
2
+
3
+ module Glimmer
4
+ class Gladiator
5
+ class Dir
6
+ include Glimmer
7
+
8
+ class << self
9
+ def local_dir
10
+ @local_dir ||= new(ENV['LOCAL_DIR'] || '.').tap do |dir|
11
+ dir.refresh
12
+ @filewatcher = Filewatcher.new(dir.path)
13
+ @thread = Thread.new(@filewatcher) do |fw|
14
+ fw.watch do |filename, event|
15
+ if @last_update.nil? || (Time.now.to_f - @last_update) > 10
16
+ dir.refresh if filename != dir.selected_child_path
17
+ end
18
+ @last_update = Time.now.to_f
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ attr_accessor :selected_child, :filter, :children, :filtered_path_options
26
+ attr_reader :path, :display_path
27
+
28
+ def initialize(path)
29
+ @path = @display_path = path
30
+ self.filtered_path_options = []
31
+ end
32
+
33
+ def children
34
+ @children ||= retrieve_children
35
+ end
36
+
37
+ def retrieve_children
38
+ ::Dir.glob(::File.join(@path, '*')).map {|p| ::File.file?(p) ? Gladiator::File.new(p) : Gladiator::Dir.new(p)}.sort_by {|c| c.path.to_s.downcase }.sort_by {|c| c.class.name }
39
+ end
40
+
41
+ def refresh
42
+ new_all_children = retrieve_all_children
43
+ new_children = retrieve_children
44
+ async_exec do
45
+ @all_children = new_all_children
46
+ @children ||= []
47
+ @children.clear
48
+ new_children.each do |child|
49
+ @children << child
50
+ end
51
+ end
52
+ end
53
+
54
+ def filter=(value)
55
+ if value.to_s.empty?
56
+ @filter = nil
57
+ else
58
+ @filter = value
59
+ end
60
+ self.filtered_path_options = filtered.to_a.map(&:display_path)
61
+ end
62
+
63
+ def filtered
64
+ return if filter.nil?
65
+ all_children_files.select do |child|
66
+ child.path.downcase.include?(filter.downcase) ||
67
+ child.path.downcase.gsub(/[_\/]/, '').include?(filter.downcase)
68
+ end.sort_by {|c| c.path.to_s.downcase}
69
+ end
70
+
71
+ def all_children
72
+ @all_children ||= retrieve_all_children
73
+ end
74
+
75
+ def retrieve_all_children
76
+ ::Dir.glob(::File.join(@path, '**', '*')).map {|p| ::File.file?(p) ? Gladiator::File.new(p) : Gladiator::Dir.new(p)}
77
+ end
78
+
79
+ def all_children_files
80
+ all_children.select {|child| child.is_a?(Gladiator::File) }
81
+ end
82
+
83
+ def selected_child_path=(selected_path)
84
+ if selected_path && ::File.file?(selected_path)
85
+ @selected_child&.write_dirty_content
86
+ new_child = Gladiator::File.new(selected_path)
87
+ begin
88
+ unless new_child.dirty_content.nil?
89
+ self.selected_child&.stop_filewatcher
90
+ self.selected_child = new_child
91
+ self.selected_child.start_filewatcher
92
+ end
93
+ rescue
94
+ # no op
95
+ end
96
+ end
97
+ end
98
+
99
+ def selected_child_path
100
+ @selected_child&.path
101
+ end
102
+
103
+ alias filtered_path selected_child_path
104
+ alias filtered_path= selected_child_path=
105
+
106
+ def to_s
107
+ path
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ at_exit do
114
+ Glimmer::Gladiator::Dir.local_dir.selected_child&.write_raw_dirty_content
115
+ end
116
+
@@ -0,0 +1,357 @@
1
+ require 'models/glimmer/gladiator/dir'
2
+
3
+ module Glimmer
4
+ class Gladiator
5
+ class File
6
+ include Glimmer
7
+
8
+ attr_accessor :dirty_content, :line_numbers_content, :caret_position, :selection_count, :line_number, :find_text, :replace_text, :top_index
9
+ attr_reader :path, :display_path
10
+
11
+ def initialize(path)
12
+ raise "Not a file path: #{path}" unless ::File.file?(path)
13
+ @display_path = path
14
+ @path = ::File.expand_path(path)
15
+ read_dirty_content = ::File.read(path)
16
+ begin
17
+ # test read dirty content
18
+ read_dirty_content.split("\n")
19
+ observe(self, :dirty_content) do
20
+ lines_text_size = lines.size.to_s.size
21
+ self.line_numbers_content = lines.size.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
22
+ end
23
+ self.dirty_content = read_dirty_content
24
+ observe(self, :caret_position) do
25
+ self.line_number = line_index_for_caret_position(caret_position) + 1
26
+ end
27
+ observe(self, :line_number) do
28
+ if line_number
29
+ new_caret_position = lines[0...(line_number.to_i - 1)].map(&:size).sum + line_number.to_i - 1
30
+ self.caret_position = new_caret_position unless line_index_for_caret_position(new_caret_position) == line_index_for_caret_position(caret_position)
31
+ end
32
+ end
33
+ rescue
34
+ # no op in case of a binary file
35
+ end
36
+ end
37
+
38
+ def start_filewatcher
39
+ @filewatcher = Filewatcher.new(@path)
40
+ @thread = Thread.new(@filewatcher) do |fw|
41
+ fw.watch do |filename, event|
42
+ begin
43
+ read_dirty_content = ::File.read(path)
44
+ # test read dirty content
45
+ read_dirty_content.split("\n")
46
+ async_exec do
47
+ self.dirty_content = read_dirty_content if read_dirty_content != dirty_content
48
+ end
49
+ rescue
50
+ # no op in case of a binary file
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def stop_filewatcher
57
+ @filewatcher&.stop
58
+ end
59
+
60
+ def write_dirty_content
61
+ new_dirty_content = "#{dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
62
+ self.dirty_content = new_dirty_content if new_dirty_content != self.dirty_content
63
+ ::File.write(path, dirty_content) if ::File.exists?(path) && dirty_content.to_s.strip.size > 0
64
+ end
65
+
66
+ def write_raw_dirty_content
67
+ ::File.write(path, dirty_content) if ::File.exists?(path) && dirty_content.to_s.strip.size > 0
68
+ end
69
+
70
+ def comment_line!
71
+ old_lines = lines
72
+ return if old_lines.size < 1
73
+ old_selection_count = self.selection_count
74
+ old_caret_position = self.caret_position
75
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
76
+ old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
77
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
78
+ new_lines = lines
79
+ delta = 0
80
+ line_indices_for_selection(caret_position, selection_count).reverse.each do | the_line_index |
81
+ delta = 0
82
+ the_line = old_lines[the_line_index]
83
+ if the_line.strip.start_with?('# ')
84
+ new_lines[the_line_index] = the_line.sub(/# /, '')
85
+ delta -= 2
86
+ elsif the_line.strip.start_with?('#')
87
+ new_lines[the_line_index] = the_line.sub(/#/, '')
88
+ delta -= 1
89
+ else
90
+ new_lines[the_line_index] = "# #{the_line}"
91
+ delta += 2
92
+ end
93
+ end
94
+ self.dirty_content = new_lines.join("\n")
95
+ if old_selection_count > 0
96
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
97
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
98
+ else
99
+ new_caret_position = old_caret_position + delta
100
+ new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
101
+ self.caret_position = new_caret_position
102
+ end
103
+ end
104
+
105
+ def indent!
106
+ new_lines = lines
107
+ old_lines = lines
108
+ return if old_lines.size < 1
109
+ old_selection_count = self.selection_count
110
+ old_caret_position = self.caret_position
111
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
112
+ old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
113
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
114
+ delta = 2
115
+ line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
116
+ the_line = old_lines[the_line_index]
117
+ new_lines[the_line_index] = " #{the_line}"
118
+ end
119
+ old_caret_position = self.caret_position
120
+ self.dirty_content = new_lines.join("\n")
121
+ if old_selection_count > 0
122
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
123
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
124
+ else
125
+ self.caret_position = old_caret_position + delta
126
+ end
127
+ end
128
+
129
+ def outdent!
130
+ new_lines = lines
131
+ old_lines = lines
132
+ return if old_lines.size < 1
133
+ old_selection_count = self.selection_count
134
+ old_caret_position = self.caret_position
135
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
136
+ old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
137
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
138
+ delta = 0
139
+ line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
140
+ the_line = old_lines[the_line_index]
141
+ if the_line.start_with?(' ')
142
+ new_lines[the_line_index] = the_line.sub(/ /, '')
143
+ delta = -2
144
+ elsif the_line.start_with?(' ')
145
+ new_lines[the_line_index] = the_line.sub(/ /, '')
146
+ delta = -1
147
+ end
148
+ end
149
+ self.dirty_content = new_lines.join("\n")
150
+ if old_selection_count > 0
151
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
152
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
153
+ else
154
+ new_caret_position = old_caret_position + delta
155
+ new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
156
+ self.caret_position = new_caret_position
157
+ end
158
+ end
159
+
160
+ def kill_line!
161
+ new_lines = lines
162
+ return if new_lines.size < 2
163
+ line_indices = line_indices_for_selection(caret_position, selection_count)
164
+ new_lines = new_lines[0...line_indices.first] + new_lines[(line_indices.last+1)...new_lines.size]
165
+ old_caret_position = self.caret_position
166
+ self.dirty_content = new_lines.join("\n")
167
+ self.caret_position = old_caret_position
168
+ end
169
+
170
+ def duplicate_line!
171
+ new_lines = lines
172
+ old_lines = lines
173
+ return if old_lines.size < 1
174
+ old_selection_count = self.selection_count
175
+ old_caret_position = self.caret_position
176
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
177
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
178
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
179
+ the_line_indices = line_indices_for_selection(caret_position, selection_count)
180
+ the_lines = lines_for_selection(caret_position, selection_count)
181
+ delta = the_lines.join("\n").size + 1
182
+ the_lines.each_with_index do |the_line, i|
183
+ new_lines.insert(the_line_indices.first + i, the_line)
184
+ end
185
+ self.dirty_content = new_lines.join("\n")
186
+ if old_selection_count > 0
187
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
188
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
189
+ else
190
+ self.caret_position = old_caret_position + delta
191
+ end
192
+ end
193
+
194
+ def find_next
195
+ return if find_text.to_s.empty?
196
+ all_lines = lines
197
+ the_line_index = line_index_for_caret_position(caret_position)
198
+ all_lines.rotate(the_line_index + 1).each_with_index do |the_line, the_index|
199
+ the_index = (the_index + the_line_index + 1)%all_lines.size
200
+ if the_line.downcase.include?(find_text.to_s.downcase)
201
+ self.caret_position = the_line.downcase.index(find_text.to_s.downcase) + caret_position_for_line_index(the_index)
202
+ self.selection_count = find_text.to_s.size
203
+ return
204
+ end
205
+ end
206
+ end
207
+
208
+ def find_previous
209
+ return if find_text.to_s.empty?
210
+ all_lines = lines
211
+ the_line_index = line_index_for_caret_position(caret_position)
212
+ all_lines.rotate(the_line_index).each_with_index.map do |the_line, the_index|
213
+ the_index = (the_index + the_line_index)%all_lines.size
214
+ [the_line, the_index]
215
+ end.reverse.each do |the_line, the_index|
216
+ if the_line.downcase.include?(find_text.to_s.downcase)
217
+ self.caret_position = the_line.downcase.index(find_text.to_s.downcase) + caret_position_for_line_index(the_index)
218
+ self.selection_count = find_text.to_s.size
219
+ return
220
+ end
221
+ end
222
+ end
223
+
224
+ def ensure_find_next
225
+ return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
226
+ find_next unless dirty_content[caret_position.to_i, find_text.to_s.size] == find_text
227
+ end
228
+
229
+ def replace_next!
230
+ return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
231
+ ensure_find_next
232
+ new_dirty_content = dirty_content
233
+ new_dirty_content[caret_position, find_text.size] = replace_text.to_s
234
+ self.dirty_content = new_dirty_content
235
+ find_next
236
+ end
237
+
238
+ def page_up
239
+ self.line_number = [(self.line_number - 15), 1].max
240
+ end
241
+
242
+ def page_down
243
+ self.line_number = [(self.line_number + 15), lines.size].min
244
+ end
245
+
246
+ def home
247
+ self.line_number = 1
248
+ end
249
+
250
+ def end
251
+ self.line_number = lines.size
252
+ end
253
+
254
+ def move_up!
255
+ old_lines = lines
256
+ return if old_lines.size < 2
257
+ old_selection_count = self.selection_count
258
+ old_caret_position = self.caret_position
259
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
260
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
261
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
262
+ new_lines = lines
263
+ the_line_indices = line_indices_for_selection(caret_position, selection_count)
264
+ the_lines = lines_for_selection(caret_position, selection_count)
265
+ new_line_index = [the_line_indices.first - 1, 0].max
266
+ delta = -1 * (new_lines[new_line_index].size + 1)
267
+ new_lines[the_line_indices.first..the_line_indices.last] = []
268
+ new_lines[new_line_index...new_line_index] = the_lines
269
+ self.dirty_content = new_lines.join("\n")
270
+ if old_selection_count > 0
271
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index) + delta
272
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position + delta)
273
+ else
274
+ self.caret_position = old_caret_position + delta
275
+ end
276
+ end
277
+
278
+ def move_down!
279
+ old_lines = lines
280
+ return if old_lines.size < 2
281
+ old_selection_count = self.selection_count
282
+ old_caret_position = self.caret_position
283
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
284
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
285
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
286
+ new_lines = lines
287
+ the_line_indices = line_indices_for_selection(caret_position, selection_count)
288
+ the_lines = lines_for_selection(caret_position, selection_count)
289
+ new_line_index = [the_line_indices.first + 1, new_lines.size - 1].min
290
+ delta = new_lines[new_line_index].size + 1
291
+ new_lines[the_line_indices.first..the_line_indices.last] = []
292
+ new_lines[new_line_index...new_line_index] = the_lines
293
+ self.dirty_content = new_lines.join("\n")
294
+ if old_selection_count > 0
295
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index) + delta
296
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position + delta)
297
+ else
298
+ self.caret_position = old_caret_position + delta
299
+ end
300
+ end
301
+
302
+ def lines
303
+ dirty_content.split("\n")
304
+ end
305
+
306
+ def line_for_caret_position(caret_position)
307
+ lines[line_index_for_caret_position(caret_position.to_i)]
308
+ end
309
+
310
+ def line_index_for_caret_position(caret_position)
311
+ dirty_content[0...caret_position.to_i].count("\n")
312
+ end
313
+
314
+ def caret_position_for_line_index(line_index)
315
+ lines[0...line_index].join("\n").size + 1
316
+ end
317
+
318
+ def caret_position_for_caret_position_start_of_line(caret_position)
319
+ caret_position_for_line_index(line_index_for_caret_position(caret_position))
320
+ end
321
+
322
+ def line_caret_positions_for_selection(caret_position, selection_count)
323
+ line_indices = line_indices_for_selection(caret_position, selection_count)
324
+ line_caret_positions = line_indices.map { |line_index| caret_position_for_line_index(line_index) }.to_a
325
+ end
326
+
327
+ def end_caret_position_line_index(caret_position, selection_count)
328
+ end_caret_position = caret_position + selection_count.to_i
329
+ end_caret_position -= 1 if dirty_content[end_caret_position - 1] == "\n"
330
+ end_line_index = line_index_for_caret_position(end_caret_position)
331
+ end
332
+
333
+ def lines_for_selection(caret_position, selection_count)
334
+ line_indices = line_indices_for_selection(caret_position, selection_count)
335
+ lines[line_indices.first..line_indices.last]
336
+ end
337
+
338
+ def line_indices_for_selection(caret_position, selection_count)
339
+ start_line_index = line_index_for_caret_position(caret_position)
340
+ if selection_count.to_i > 0
341
+ end_line_index = end_caret_position_line_index(caret_position, selection_count)
342
+ else
343
+ end_line_index = start_line_index
344
+ end
345
+ (start_line_index..end_line_index).to_a
346
+ end
347
+
348
+ def children
349
+ []
350
+ end
351
+
352
+ def to_s
353
+ path
354
+ end
355
+ end
356
+ end
357
+ end
@@ -0,0 +1,319 @@
1
+ require 'models/glimmer/gladiator/dir'
2
+ require 'models/glimmer/gladiator/file'
3
+
4
+ Clipboard.implementation = Clipboard::Java
5
+ Clipboard.copy(Clipboard.paste) # pre-initialize library to avoid slowdown during use
6
+
7
+ module Glimmer
8
+ # Gladiator (Glimmer Editor)
9
+ class Gladiator
10
+ include Glimmer::UI::CustomShell
11
+
12
+ ## Add options like the following to configure CustomShell by outside consumers
13
+ #
14
+ # options :title, :background_color
15
+ # option :width, 320
16
+ # option :height, 240
17
+
18
+ ## Uncomment before_body block to pre-initialize variables to use in body
19
+ #
20
+ #
21
+ before_body {
22
+ Display.setAppName('Gladiator')
23
+ @config_file_path = '.gladiator'
24
+ @config = {}
25
+ Gladiator::Dir.local_dir.all_children # pre-caches children
26
+ load_config
27
+ @display = display {
28
+ on_event_keydown { |key_event|
29
+ if Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == 'f'
30
+ if @text.swt_widget.getSelectionText && @text.swt_widget.getSelectionText.size > 0
31
+ @find_text.swt_widget.setText @text.swt_widget.getSelectionText
32
+ end
33
+ @find_text.swt_widget.selectAll
34
+ @find_text.swt_widget.setFocus
35
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command, :shift) && key_event.character.chr.downcase == 'c'
36
+ Clipboard.copy(Gladiator::Dir.local_dir.selected_child.path)
37
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command, :shift) && key_event.character.chr.downcase == 'g'
38
+ Gladiator::Dir.local_dir.selected_child.find_previous
39
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == 'g'
40
+ Gladiator::Dir.local_dir.selected_child.find_next
41
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == 'l'
42
+ @line_number_text.swt_widget.selectAll
43
+ @line_number_text.swt_widget.setFocus
44
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == 'r'
45
+ @filter_text.swt_widget.selectAll
46
+ @filter_text.swt_widget.setFocus
47
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == 't'
48
+ @tree.swt_widget.setFocus
49
+ end
50
+ }
51
+ }
52
+ }
53
+
54
+ ## Uncomment after_body block to setup observers for widgets in body
55
+ #
56
+ after_body {
57
+ observe(Gladiator::Dir.local_dir, 'selected_child.line_numbers_content') do
58
+ if @last_line_numbers_content != Gladiator::Dir.local_dir.selected_child.line_numbers_content
59
+ body_root.pack_same_size
60
+ @last_line_numbers_content = Gladiator::Dir.local_dir.selected_child.line_numbers_content
61
+ end
62
+ end
63
+ observe(Gladiator::Dir.local_dir, 'selected_child.caret_position') do
64
+ save_config
65
+ end
66
+ observe(Gladiator::Dir.local_dir, 'selected_child.top_index') do
67
+ save_config
68
+ end
69
+ }
70
+
71
+ ## Add widget content inside custom shell body
72
+ ## Top-most widget must be a shell or another custom shell
73
+ #
74
+ body {
75
+ shell {
76
+ text "Gladiator - #{::File.expand_path(Gladiator::Dir.local_dir.path)}"
77
+ minimum_size 720, 450
78
+ size 1440, 900
79
+ grid_layout 2, false
80
+ on_event_close {
81
+ Gladiator::Dir.local_dir.selected_child&.write_dirty_content
82
+ }
83
+ on_widget_disposed {
84
+ Gladiator::Dir.local_dir.selected_child&.write_dirty_content
85
+ }
86
+ composite {
87
+ grid_layout 1, false
88
+ layout_data(:fill, :fill, false, true) {
89
+ width_hint 300
90
+ }
91
+ @filter_text = text {
92
+ layout_data :fill, :center, true, false
93
+ text bind(Gladiator::Dir.local_dir, 'filter')
94
+ on_key_pressed { |key_event|
95
+ if key_event.keyCode == swt(:tab) ||
96
+ key_event.keyCode == swt(:cr) ||
97
+ key_event.keyCode == swt(:lf) ||
98
+ key_event.keyCode == swt(:arrow_up) ||
99
+ key_event.keyCode == swt(:arrow_down)
100
+ @list.swt_widget.setFocus
101
+ elsif key_event.keyCode == swt(:esc)
102
+ @text.swt_widget.setFocus
103
+ end
104
+ }
105
+ }
106
+ composite {
107
+ layout_data(:fill, :fill, true, true)
108
+ @list = list(:border, :h_scroll, :v_scroll) {
109
+ layout_data(:fill, :fill, true, true) {
110
+ #exclude bind(Gladiator::Dir.local_dir, :filter) {|f| !f}
111
+ }
112
+ #visible bind(Gladiator::Dir, 'local_dir.filter') {|f| !!f}
113
+ selection bind(Gladiator::Dir.local_dir, :filtered_path)
114
+ on_widget_selected {
115
+ Gladiator::Dir.local_dir.selected_child_path = @list.swt_widget.getSelection.first
116
+ }
117
+ on_key_pressed { |key_event|
118
+ if Glimmer::SWT::SWTProxy.include?(key_event.keyCode, :cr) || Glimmer::SWT::SWTProxy.include?(key_event.keyCode, :lf)
119
+ Gladiator::Dir.local_dir.selected_child_path = @list.swt_widget.getSelection.first
120
+ @text.swt_widget.setFocus
121
+ end
122
+ }
123
+ }
124
+ @tree = tree(:virtual, :border, :h_scroll, :v_scroll) {
125
+ layout_data(:fill, :fill, true, true) {
126
+ #exclude bind(Gladiator::Dir.local_dir, :filter) {|f| !!f}
127
+ }
128
+ #visible bind(Gladiator::Dir, 'local_dir.filter') {|f| !f}
129
+ items bind(Gladiator::Dir, :local_dir), tree_properties(children: :children, text: :display_path)
130
+ on_widget_selected {
131
+ Gladiator::Dir.local_dir.selected_child_path = @tree.swt_widget.getSelection.first.getText
132
+ }
133
+ on_key_pressed { |key_event|
134
+ if Glimmer::SWT::SWTProxy.include?(key_event.keyCode, :cr) || Glimmer::SWT::SWTProxy.include?(key_event.keyCode, :lf)
135
+ Gladiator::Dir.local_dir.selected_child_path = @tree.swt_widget.getSelection.first.getText
136
+ @text.swt_widget.setFocus
137
+ end
138
+ }
139
+ on_paint_control {
140
+ root_item = @tree.swt_widget.getItems.first
141
+ if root_item && !root_item.getExpanded
142
+ root_item.setExpanded true
143
+ end
144
+ }
145
+ }
146
+ }
147
+ }
148
+ composite {
149
+ grid_layout 1, false
150
+ layout_data :fill, :fill, true, true
151
+ composite {
152
+ grid_layout 2, false
153
+ @file_path_label = styled_text(:none) {
154
+ layout_data(:fill, :fill, true, false) {
155
+ horizontal_span 2
156
+ }
157
+ background color(:widget_background)
158
+ editable false
159
+ caret nil
160
+ text bind(Gladiator::Dir.local_dir, 'selected_child.path')
161
+ on_mouse_up {
162
+ @file_path_label.swt_widget.selectAll
163
+ }
164
+ on_focus_lost {
165
+ @file_path_label.swt_widget.setSelection(0, 0)
166
+ }
167
+ }
168
+ label {
169
+ text 'Line:'
170
+ }
171
+ @line_number_text = text {
172
+ layout_data(:fill, :fill, true, false) {
173
+ minimum_width 300
174
+ }
175
+ text bind(Gladiator::Dir.local_dir, 'selected_child.line_number', on_read: :to_s, on_write: :to_i)
176
+ on_key_pressed { |key_event|
177
+ if key_event.keyCode == swt(:cr)
178
+ @text.swt_widget.setFocus
179
+ end
180
+ }
181
+ }
182
+ label {
183
+ text 'Find:'
184
+ }
185
+ @find_text = text {
186
+ layout_data(:fill, :fill, true, false) {
187
+ minimum_width 200
188
+ }
189
+ text bind(Gladiator::Dir.local_dir, 'selected_child.find_text')
190
+ on_key_pressed { |key_event|
191
+ if key_event.keyCode == swt(:cr)
192
+ Gladiator::Dir.local_dir.selected_child.find_next
193
+ elsif key_event.keyCode == swt(:esc)
194
+ @text.swt_widget.setFocus
195
+ end
196
+ }
197
+ }
198
+ label {
199
+ text 'Replace:'
200
+ }
201
+ @replace_text = text {
202
+ layout_data(:fill, :fill, true, false) {
203
+ minimum_width 200
204
+ }
205
+ text bind(Gladiator::Dir.local_dir, 'selected_child.replace_text')
206
+ on_focus_gained {
207
+ Gladiator::Dir.local_dir.selected_child.ensure_find_next
208
+ }
209
+ on_key_pressed { |key_event|
210
+ if key_event.keyCode == swt(:cr)
211
+ Gladiator::Dir.local_dir.selected_child.replace_next!
212
+ elsif key_event.keyCode == swt(:esc)
213
+ @text.swt_widget.setFocus
214
+ end
215
+ }
216
+ }
217
+ }
218
+ composite {
219
+ layout_data :fill, :fill, true, true
220
+ grid_layout 2, false
221
+ @line_numbers_text = text(:multi) {
222
+ layout_data(:right, :fill, false, true)
223
+ font name: 'Consolas', height: 15
224
+ background color(:widget_background)
225
+ foreground rgb(75, 75, 75)
226
+ text bind(Gladiator::Dir.local_dir, 'selected_child.line_numbers_content')
227
+ top_index bind(Gladiator::Dir.local_dir, 'selected_child.top_index')
228
+ on_focus_gained {
229
+ @text&.swt_widget.setFocus
230
+ }
231
+ on_key_pressed {
232
+ @text&.swt_widget.setFocus
233
+ }
234
+ on_mouse_up {
235
+ @text&.swt_widget.setFocus
236
+ }
237
+ }
238
+ @text = text(:multi, :border, :h_scroll, :v_scroll) {
239
+ layout_data :fill, :fill, true, true
240
+ font name: 'Consolas', height: 15
241
+ foreground rgb(75, 75, 75)
242
+ text bind(Gladiator::Dir.local_dir, 'selected_child.dirty_content')
243
+ focus true
244
+ caret_position bind(Gladiator::Dir.local_dir, 'selected_child.caret_position')
245
+ selection_count bind(Gladiator::Dir.local_dir, 'selected_child.selection_count')
246
+ top_index bind(Gladiator::Dir.local_dir, 'selected_child.top_index')
247
+ on_focus_lost {
248
+ Gladiator::Dir.local_dir.selected_child&.write_dirty_content
249
+ }
250
+ on_key_pressed { |key_event|
251
+ if Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == '/'
252
+ Gladiator::Dir.local_dir.selected_child.comment_line!
253
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == 'k'
254
+ Gladiator::Dir.local_dir.selected_child.kill_line!
255
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == 'd'
256
+ Gladiator::Dir.local_dir.selected_child.duplicate_line!
257
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == '['
258
+ Gladiator::Dir.local_dir.selected_child.outdent!
259
+ elsif Glimmer::SWT::SWTProxy.include?(key_event.stateMask, :command) && key_event.character.chr.downcase == ']'
260
+ Gladiator::Dir.local_dir.selected_child.indent!
261
+ elsif key_event.keyCode == swt(:page_up)
262
+ Gladiator::Dir.local_dir.selected_child.page_up
263
+ key_event.doit = false
264
+ elsif key_event.keyCode == swt(:page_down)
265
+ Gladiator::Dir.local_dir.selected_child.page_down
266
+ key_event.doit = false
267
+ elsif key_event.keyCode == swt(:home)
268
+ Gladiator::Dir.local_dir.selected_child.home
269
+ key_event.doit = false
270
+ elsif key_event.keyCode == swt(:end)
271
+ Gladiator::Dir.local_dir.selected_child.end
272
+ key_event.doit = false
273
+ elsif key_event.stateMask == swt(:command) && key_event.keyCode == swt(:arrow_up)
274
+ Gladiator::Dir.local_dir.selected_child.move_up!
275
+ key_event.doit = false
276
+ elsif key_event.stateMask == swt(:command) && key_event.keyCode == swt(:arrow_down)
277
+ Gladiator::Dir.local_dir.selected_child.move_down!
278
+ key_event.doit = false
279
+ end
280
+ }
281
+ on_verify_text { |verify_event|
282
+ key_code = verify_event.keyCode
283
+ case key_code
284
+ when swt(:tab)
285
+ verify_event.text = ' '
286
+ end
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ def load_config
295
+ if ::File.exists?(@config_file_path)
296
+ config_yaml = ::File.read(@config_file_path)
297
+ return if config_yaml.to_s.strip.empty?
298
+ @config = YAML.load(config_yaml)
299
+ Gladiator::Dir.local_dir.selected_child_path = @config[:selected_child_path] if @config[:selected_child_path]
300
+ Gladiator::Dir.local_dir.selected_child&.caret_position = Gladiator::Dir.local_dir.selected_child&.caret_position_for_caret_position_start_of_line(@config[:caret_position]) if @config[:caret_position]
301
+ Gladiator::Dir.local_dir.selected_child&.top_index = @config[:top_index] if @config[:top_index]
302
+ end
303
+ end
304
+
305
+ def save_config
306
+ child = Gladiator::Dir.local_dir.selected_child
307
+ return if child.nil?
308
+ @config = {
309
+ selected_child_path: child.path,
310
+ caret_position: child.caret_position,
311
+ top_index: child.top_index,
312
+ }
313
+ config_yaml = YAML.dump(@config)
314
+ ::File.write(@config_file_path, config_yaml) unless config_yaml.to_s.empty?
315
+ rescue => e
316
+ puts e.full_message
317
+ end
318
+ end
319
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: glimmer-cs-gladiator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andy Maleh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.7.0
19
+ name: glimmer
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.0
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.1
33
+ name: filewatcher
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.1.1
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 1.3.4
47
+ name: clipboard
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.4
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 3.5.0
61
+ name: rspec
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.5.0
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '='
73
+ - !ruby/object:Gem::Version
74
+ version: 2.3.9
75
+ name: jeweler
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 2.3.9
83
+ - !ruby/object:Gem::Dependency
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ name: simplecov
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Gladiator (short for Glimmer Editor) is a Glimmer sample project under
98
+ on-going development. It is not intended to be a full-fledged editor by any means,
99
+ yet mostly a fun educational exercise in using Glimmer to build a text editor. Gladiator
100
+ is also a personal tool for shaping an editor exactly the way I like. I leave building
101
+ truly professional text editors to software tooling experts who would hopefully
102
+ use Glimmer one day.
103
+ email: andy.am@gmail.com
104
+ executables:
105
+ - gladiator
106
+ extensions: []
107
+ extra_rdoc_files:
108
+ - LICENSE.txt
109
+ - README.md
110
+ files:
111
+ - LICENSE.txt
112
+ - README.md
113
+ - bin/gladiator
114
+ - bin/gladiator_runner.rb
115
+ - lib/glimmer-cs-gladiator.rb
116
+ - lib/models/glimmer/gladiator/dir.rb
117
+ - lib/models/glimmer/gladiator/file.rb
118
+ - lib/views/glimmer/gladiator.rb
119
+ homepage: http://github.com/AndyObtiva/glimmer-cs-gladiator
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubygems_version: 3.0.6
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Gladiator (Glimmer Editor) - Glimmer Custom Shell
142
+ test_files: []