glimmer-cs-gladiator 0.4.1 → 0.5.4
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/README.md +23 -21
- data/VERSION +1 -1
- data/bin/gladiator_runner.rb +2 -1
- data/lib/glimmer-cs-gladiator.rb +1 -1
- data/lib/models/glimmer/gladiator/command.rb +4 -4
- data/lib/models/glimmer/gladiator/dir.rb +78 -42
- data/lib/models/glimmer/gladiator/file.rb +180 -102
- data/lib/views/glimmer/gladiator.rb +465 -297
- data/lib/views/glimmer/gladiator/text_editor.rb +34 -27
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d31e7dd5189317eaeb3a91a175a050ffaaeb4e82d6c8709167477d26dd7bf237
|
4
|
+
data.tar.gz: 370a540665a9d7f3d08154f1e98530521f72e9a720f35fa83f982747e2e5bf5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7af2ad9407c442ad6634a843ad3b5fc6cac47dadbd84ec0dc0b19f8d38b858950648a951383be811f040232ab426cbf0dffc1ad1d003a33a6a3cefd07d6c6832
|
7
|
+
data.tar.gz: a9d30dc68de3f7de8fa25c9860c57885892e4ceeea734c63bc4fd2b257736afc0e1b0c7a062d03af93991d03d05907dbfbe058adcf6e13d695ce524167ce5aef
|
data/README.md
CHANGED
@@ -1,20 +1,21 @@
|
|
1
|
-
# <img src='https://raw.githubusercontent.com/AndyObtiva/glimmer-cs-gladiator/master/images/glimmer-cs-gladiator-logo.svg' height=85 /> Gladiator 0.4
|
1
|
+
# <img src='https://raw.githubusercontent.com/AndyObtiva/glimmer-cs-gladiator/master/images/glimmer-cs-gladiator-logo.svg' height=85 /> Gladiator 0.5.4 - [Ugliest Text Editor Ever](https://www.reddit.com/r/ruby/comments/hgve8k/gladiator_glimmer_editor_ugliest_text_editor_ever/)
|
2
2
|
## [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=40 /> Glimmer Custom Shell](https://github.com/AndyObtiva/glimmer-dsl-swt#custom-shell-gem)
|
3
3
|
[](http://badge.fury.io/rb/glimmer-cs-gladiator)
|
4
4
|
|
5
5
|

|
6
6
|
|
7
|
-
(
|
8
|
-
|
9
|
-
Gladiator (short for Glimmer Editor) is a [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) sample project under on-going development that demonstrates how to build a text editor in Ruby using [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) (JRuby Desktop Development GUI Library).
|
7
|
+
Gladiator (short for Glimmer Editor) is a [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) sample beta project under on-going development that demonstrates how to build a text editor in Ruby using [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) (JRuby Desktop Development GUI Library).
|
10
8
|
It is not intended to be a full-fledged editor by any means, yet mostly a fun educational exercise in using [Glimmer](https://github.com/AndyObtiva/glimmer).
|
11
|
-
Gladiator is also a personal tool for shaping an editor exactly the way I like, with all the keyboard shortcuts I prefer.
|
9
|
+
Gladiator is also a personal tool for shaping an editor exactly the way I like, with all the keyboard shortcuts I prefer.
|
12
10
|
I leave building truly professional text editors to software tooling experts who would hopefully use [Glimmer](https://github.com/AndyObtiva/glimmer) one day. Otherwise, I have been happily using Gladiator to develop all my [open-source projects](https://github.com/AndyObtiva) since May of 2020.
|
13
11
|
|
14
12
|
## Features
|
15
13
|
|
16
14
|
Gladiator currently supports the following text editing features (including keyboard shortcuts with Mac CMD=CTRL on Windows/Linux):
|
17
15
|
- Ruby Syntax Highlighting with colors
|
16
|
+
- Open Project from File Menu (CMD+SHIFT+P)
|
17
|
+
- Scratchpad for running arbitrary Ruby/Glimmer code without saving to disk (CMD+SHIFT+S)
|
18
|
+
- Run Ruby code (CMD+SHIFT+R)
|
18
19
|
- File explorer navigation with context menu to open file, rename, delete, add new file, add new directory, or refresh tree (CMD+T)
|
19
20
|
- File lookup by name ignoring slashes, underscores, and dots to ease lookup (CMD+R)
|
20
21
|
- Watch open file for external changes to automatically refresh in editor
|
@@ -24,7 +25,7 @@ Gladiator currently supports the following text editing features (including keyb
|
|
24
25
|
- Jump to Line (CMD+L)
|
25
26
|
- Multiple tab support (CMD+SHIFT+[ & CMD+SHIFT+] for tab navigation. CMD+1-9 to jump to a specific tab)
|
26
27
|
- Remember opened tabs, caret position, top line, window size, and window location
|
27
|
-
- Autosave on focus out/quit/open new file
|
28
|
+
- Autosave on focus out/quit/open new file
|
28
29
|
- Duplicate Line(s)/selection (CMD+D)
|
29
30
|
- Kill Line(s)/selection (CMD+K)
|
30
31
|
- Move line/selection up (CMD+UP)
|
@@ -32,15 +33,14 @@ Gladiator currently supports the following text editing features (including keyb
|
|
32
33
|
- Comment/Uncomment line/selection (CMD+/)
|
33
34
|
- Indent/Unindent line/selection (CMD+] & CMD+[)
|
34
35
|
- Insert/Prefix New Line (CMD+ENTER & CMD+SHIFT+ENTER)
|
35
|
-
-
|
36
|
-
-
|
37
|
-
- Change Split Orientation to Horizontal/Vertical via View Menu (CMD+SHIFT+O)
|
36
|
+
- Split Pane & Change Split Orientation to Horizontal/Vertical via View Menu (CMD+SHIFT+O)
|
37
|
+
- Drag and Drop Text Editor Split Screen (drag a file from File Tree or File Lookup List, and it splits the pane)
|
38
38
|
|
39
39
|
## Platforms
|
40
40
|
|
41
41
|
- Mac: Gladiator works best on the Mac. This is the platform it is most used on and receives the most maintenance for.
|
42
|
-
- Windows: Gladiator works OK on Windows, but has some annoying bugs.
|
43
|
-
- Linux: Gladiator works with
|
42
|
+
- Windows: Gladiator works OK on Windows, but has some annoying bugs. Contributers could help fix.
|
43
|
+
- Linux: Gladiator works with handicaps on Linux (performing some text editing operations causes scroll jitter). Contributers could help fix.
|
44
44
|
|
45
45
|
## Pre-requisites
|
46
46
|
|
@@ -78,6 +78,12 @@ You may run the `gladiator` command to bring up the text editor in the project d
|
|
78
78
|
gladiator
|
79
79
|
```
|
80
80
|
|
81
|
+
On Linux, you may need to run with extra memory via this command instead:
|
82
|
+
|
83
|
+
```
|
84
|
+
gladiator -J-Xmx1200M
|
85
|
+
```
|
86
|
+
|
81
87
|
On Windows, you may need to run with extra memory via this command instead:
|
82
88
|
|
83
89
|
```
|
@@ -100,11 +106,11 @@ bin/gladiator relative-or-absolute-path/to/project
|
|
100
106
|
|
101
107
|
### Glimmer Custom Shell Reuse
|
102
108
|
|
103
|
-
To reuse Gladiator as a Glimmer Custom Shell inside another Glimmer application, add the
|
109
|
+
To reuse Gladiator as a Glimmer Custom Shell inside another Glimmer application, add the
|
104
110
|
following to the application's `Gemfile`:
|
105
111
|
|
106
112
|
```
|
107
|
-
gem 'glimmer-cs-gladiator', '~> 0.4
|
113
|
+
gem 'glimmer-cs-gladiator', '~> 0.5.4'
|
108
114
|
```
|
109
115
|
|
110
116
|
Run:
|
@@ -117,7 +123,7 @@ And, then instantiate the Gladiator [custom shell](https://github.com/AndyObtiva
|
|
117
123
|
|
118
124
|
## Env Var Options
|
119
125
|
|
120
|
-
Gladiator opens with the current directory as the root by default.
|
126
|
+
Gladiator opens with the current directory as the root by default.
|
121
127
|
If you would like to open another directory, set `LOCAL_DIR` environment variable.
|
122
128
|
|
123
129
|
Example:
|
@@ -133,13 +139,9 @@ Opens Gladiator with "/Users/User/code" as the root directory.
|
|
133
139
|
Gladiator automatically saves configuration data in a `.gladiator` file at the directory it is run from.
|
134
140
|
|
135
141
|
It currently remembers:
|
136
|
-
- Last opened
|
137
|
-
-
|
138
|
-
-
|
139
|
-
- Window size
|
140
|
-
- Opened tabs
|
141
|
-
- Split tabs
|
142
|
-
- Ignore Paths
|
142
|
+
- Last opened files (in both split panes if split)
|
143
|
+
- Window size and position
|
144
|
+
- Ignore Paths (default: '.gladiator', '.git', 'coverage', 'packages', 'node_modules', 'tmp', 'vendor')
|
143
145
|
|
144
146
|
## Gotcha
|
145
147
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4
|
1
|
+
0.5.4
|
data/bin/gladiator_runner.rb
CHANGED
data/lib/glimmer-cs-gladiator.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Glimmer
|
2
2
|
class Gladiator
|
3
|
-
class Command
|
3
|
+
class Command
|
4
4
|
class << self
|
5
5
|
include Glimmer
|
6
6
|
|
@@ -10,10 +10,10 @@ module Glimmer
|
|
10
10
|
|
11
11
|
def command_history_for(file)
|
12
12
|
# keeping a first command to make redo support work by remembering next command after undoing all
|
13
|
-
command_history[file] ||= [Command.new(file)]
|
13
|
+
command_history[file] ||= [Command.new(file)]
|
14
14
|
end
|
15
15
|
|
16
|
-
def do(file, method = nil, command: nil)
|
16
|
+
def do(file, method = nil, command: nil)
|
17
17
|
command ||= Command.new(file, method)
|
18
18
|
command_history_for(file)&.last&.next_command = command
|
19
19
|
command.do
|
@@ -21,7 +21,7 @@ module Glimmer
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def undo(file)
|
24
|
-
return if command_history_for(file).size <= 1
|
24
|
+
return if command_history_for(file).size <= 1
|
25
25
|
command = command_history_for(file).pop
|
26
26
|
command&.undo
|
27
27
|
end
|
@@ -4,39 +4,49 @@ module Glimmer
|
|
4
4
|
class Gladiator
|
5
5
|
class Dir
|
6
6
|
include Glimmer
|
7
|
+
include Glimmer::DataBinding::ObservableModel
|
7
8
|
|
8
|
-
|
9
|
+
attr_accessor :selected_child, :filter, :children, :filtered_path_options, :filtered_path, :display_path, :ignore_paths
|
10
|
+
attr_reader :name, :parent, :path
|
11
|
+
attr_writer :all_children
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@thread = Thread.new(@filewatcher) do |fw|
|
13
|
+
def initialize(path, project_dir = nil)
|
14
|
+
@project_dir = project_dir
|
15
|
+
if is_local_dir
|
16
|
+
@filewatcher = Filewatcher.new(path)
|
17
|
+
Thread.new(@filewatcher) do |fw|
|
18
|
+
begin
|
17
19
|
fw.watch do |filename, event|
|
18
|
-
|
19
|
-
|
20
|
+
# TODO do fine grained processing of events for enhanced performance (e.g. dir refresh vs file change)
|
21
|
+
# TODO do fine grained file change only without a refresh delay for enhanced performance
|
22
|
+
begin
|
23
|
+
if !@refresh_in_progress && !filename.include?('new_file') && (event != :updated || find_child_file(filename).nil?)
|
24
|
+
Thread.new {
|
25
|
+
refresh
|
26
|
+
}
|
27
|
+
end
|
28
|
+
rescue => e
|
29
|
+
puts e.full_message
|
20
30
|
end
|
21
|
-
@last_update = Time.now.to_f
|
22
31
|
end
|
32
|
+
rescue => e
|
33
|
+
puts e.full_message
|
23
34
|
end
|
24
35
|
end
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
attr_accessor :selected_child, :filter, :children, :filtered_path_options, :filtered_path, :display_path, :ignore_paths
|
30
|
-
attr_reader :name, :parent, :path, :is_local_dir
|
31
|
-
attr_writer :all_children
|
32
|
-
|
33
|
-
def initialize(path, is_local_dir = false)
|
34
|
-
@is_local_dir = is_local_dir
|
36
|
+
end
|
35
37
|
self.path = ::File.expand_path(path)
|
36
38
|
@name = ::File.basename(::File.expand_path(path))
|
37
|
-
@ignore_paths = ['packages', 'tmp', 'vendor']
|
39
|
+
@ignore_paths = ['.gladiator', '.git', 'coverage', 'packages', 'node_modules', 'tmp', 'vendor']
|
38
40
|
self.filtered_path_options = []
|
39
41
|
end
|
42
|
+
|
43
|
+
def is_local_dir
|
44
|
+
@project_dir.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def project_dir
|
48
|
+
@project_dir || self
|
49
|
+
end
|
40
50
|
|
41
51
|
def path=(the_path)
|
42
52
|
@path = the_path
|
@@ -44,7 +54,7 @@ module Glimmer
|
|
44
54
|
end
|
45
55
|
|
46
56
|
def generate_display_path
|
47
|
-
is_local_dir ? path : @display_path = @path.sub(
|
57
|
+
is_local_dir ? path : @display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
|
48
58
|
end
|
49
59
|
|
50
60
|
def name=(the_name)
|
@@ -61,20 +71,36 @@ module Glimmer
|
|
61
71
|
|
62
72
|
def retrieve_children
|
63
73
|
@children = ::Dir.glob(::File.join(@path, '*')).reject do |p|
|
64
|
-
# TODO make sure to configure ignore_paths in a preferences dialog
|
65
|
-
|
74
|
+
# TODO make sure to configure ignore_paths in a preferences dialog
|
75
|
+
project_dir.ignore_paths.reduce(false) do |result, ignore_path|
|
66
76
|
result || p.include?(ignore_path)
|
67
77
|
end
|
68
|
-
end.map do |p|
|
69
|
-
::File.file?(p) ?
|
70
|
-
end.sort_by do |c|
|
71
|
-
c.path.to_s.downcase
|
72
|
-
end.sort_by do |c|
|
78
|
+
end.map do |p|
|
79
|
+
::File.file?(p) ? File.new(p, project_dir) : Dir.new(p, project_dir)
|
80
|
+
end.sort_by do |c|
|
81
|
+
c.path.to_s.downcase
|
82
|
+
end.sort_by do |c|
|
73
83
|
c.class.name
|
74
84
|
end.each do |child|
|
75
85
|
child.retrieve_children if child.is_a?(Dir)
|
76
86
|
end
|
77
87
|
end
|
88
|
+
|
89
|
+
def find_child_file(child_path)
|
90
|
+
depth_first_search_file(self, child_path)
|
91
|
+
end
|
92
|
+
|
93
|
+
def depth_first_search_file(dir, file_path)
|
94
|
+
dir.children.each do |child|
|
95
|
+
if child.is_a?(File)
|
96
|
+
return child if child.path.include?(file_path)
|
97
|
+
else
|
98
|
+
result = depth_first_search_file(child, file_path)
|
99
|
+
return result unless result.nil?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
nil
|
103
|
+
end
|
78
104
|
|
79
105
|
def selected_child_path_history
|
80
106
|
@selected_child_path_history ||= []
|
@@ -90,6 +116,7 @@ module Glimmer
|
|
90
116
|
|
91
117
|
def refresh(async: true, force: false)
|
92
118
|
return if @refresh_paused && !force
|
119
|
+
@refresh_in_progress = true
|
93
120
|
retrieve_children
|
94
121
|
collect_all_children
|
95
122
|
refresh_operation = lambda do
|
@@ -101,11 +128,12 @@ module Glimmer
|
|
101
128
|
else
|
102
129
|
sync_exec(&refresh_operation)
|
103
130
|
end
|
131
|
+
@refresh_in_progress = false
|
104
132
|
end
|
105
133
|
|
106
134
|
def filter=(value)
|
107
135
|
if value.to_s.empty?
|
108
|
-
@filter = nil
|
136
|
+
@filter = nil
|
109
137
|
else
|
110
138
|
@filter = value
|
111
139
|
end
|
@@ -117,8 +145,8 @@ module Glimmer
|
|
117
145
|
def filtered
|
118
146
|
return if filter.nil?
|
119
147
|
children_files = !@last_filter.to_s.empty? && filter.downcase.start_with?(@last_filter.downcase) ? @last_filtered : all_children_files
|
120
|
-
children_files.select do |child|
|
121
|
-
child_path = child.path.to_s.sub(
|
148
|
+
children_files.select do |child|
|
149
|
+
child_path = child.path.to_s.sub(project_dir.path, '')
|
122
150
|
child_path.downcase.include?(filter.downcase) ||
|
123
151
|
child_path.downcase.gsub(/[_\/.-]/, '').include?(filter.downcase.gsub(/[_\/.-]/, ''))
|
124
152
|
end.sort_by {|c| c.path.to_s.downcase}
|
@@ -141,14 +169,19 @@ module Glimmer
|
|
141
169
|
end
|
142
170
|
|
143
171
|
def selected_child_path=(selected_path)
|
144
|
-
|
145
|
-
|
146
|
-
|
172
|
+
return (project_dir.selected_child = nil) if selected_path.nil?
|
173
|
+
# scratchpad scenario
|
174
|
+
if selected_path.empty? # Scratchpad
|
175
|
+
@selected_child&.write_dirty_content
|
176
|
+
return (self.selected_child = File.new)
|
177
|
+
end
|
178
|
+
full_selected_path = selected_path.include?(project_dir.path) ? selected_path : ::File.join(project_dir.path, selected_path)
|
179
|
+
return if ::Dir.exist?(full_selected_path) ||
|
147
180
|
(selected_child && selected_child.path == full_selected_path)
|
148
181
|
selected_path = full_selected_path
|
149
182
|
if ::File.file?(selected_path)
|
150
183
|
@selected_child&.write_dirty_content
|
151
|
-
new_child =
|
184
|
+
new_child = find_child_file(selected_path)
|
152
185
|
begin
|
153
186
|
unless new_child.dirty_content.nil?
|
154
187
|
self.selected_child&.stop_filewatcher
|
@@ -168,6 +201,13 @@ module Glimmer
|
|
168
201
|
@selected_child&.path
|
169
202
|
end
|
170
203
|
|
204
|
+
def selected_child=(new_child)
|
205
|
+
return if selected_child == new_child
|
206
|
+
file_properties = @selected_child&.backup_properties if @selected_child == new_child
|
207
|
+
@selected_child = new_child
|
208
|
+
@selected_child.restore_properties(file_properties) if file_properties
|
209
|
+
end
|
210
|
+
|
171
211
|
def delete!
|
172
212
|
FileUtils.rm_rf(path)
|
173
213
|
end
|
@@ -183,11 +223,7 @@ module Glimmer
|
|
183
223
|
def hash
|
184
224
|
self.path.hash
|
185
225
|
end
|
186
|
-
end
|
226
|
+
end
|
187
227
|
end
|
188
228
|
end
|
189
|
-
|
190
|
-
at_exit do
|
191
|
-
Glimmer::Gladiator::Dir.local_dir.selected_child&.write_raw_dirty_content
|
192
|
-
end
|
193
229
|
|
@@ -1,43 +1,56 @@
|
|
1
|
-
require 'models/glimmer/gladiator/dir'
|
2
|
-
|
3
1
|
module Glimmer
|
4
2
|
class Gladiator
|
5
3
|
class File
|
6
4
|
include Glimmer
|
7
|
-
|
8
|
-
attr_accessor :dirty_content, :line_numbers_content, :selection, :line_number, :find_text, :replace_text, :top_pixel, :display_path, :case_sensitive
|
9
|
-
attr_reader :name, :path
|
10
5
|
|
11
|
-
|
12
|
-
|
6
|
+
attr_accessor :line_numbers_content, :selection, :line_number, :find_text, :replace_text, :top_pixel, :display_path, :case_sensitive
|
7
|
+
attr_reader :name, :path, :project_dir
|
8
|
+
|
9
|
+
def initialize(path='', project_dir=nil)
|
10
|
+
raise "Not a file path: #{path}" if path.nil? || (!path.empty? && !::File.file?(path))
|
11
|
+
@project_dir = project_dir
|
13
12
|
@command_history = []
|
14
|
-
@name = ::File.basename(path)
|
15
|
-
self.path = ::File.expand_path(path)
|
13
|
+
@name = path.empty? ? 'Scratchpad' : ::File.basename(path)
|
14
|
+
self.path = ::File.expand_path(path) unless path.empty?
|
16
15
|
@top_pixel = 0
|
17
16
|
@selection_count = 0
|
18
17
|
@selection = Point.new(0, 0 + @selection_count)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
observe(self, :line_number) do
|
33
|
-
if line_number
|
34
|
-
line_index = line_number - 1
|
35
|
-
new_caret_position = caret_position_for_line_index(line_index)
|
36
|
-
self.caret_position = new_caret_position unless self.caret_position && line_index_for_caret_position(new_caret_position) == line_index_for_caret_position(caret_position)
|
18
|
+
@line_number = 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def init_content
|
22
|
+
unless @init
|
23
|
+
@init = true
|
24
|
+
begin
|
25
|
+
# test read dirty content
|
26
|
+
observe(self, :dirty_content) do
|
27
|
+
lines_text_size = lines.size.to_s.size
|
28
|
+
old_top_pixel = top_pixel
|
29
|
+
self.line_numbers_content = lines.size.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
|
30
|
+
self.top_pixel = old_top_pixel
|
37
31
|
end
|
32
|
+
the_dirty_content = read_dirty_content
|
33
|
+
the_dirty_content.split("\n") # test that it is not a binary file (crashes to rescue block otherwise)
|
34
|
+
self.dirty_content = the_dirty_content
|
35
|
+
observe(self, :selection) do
|
36
|
+
new_line_number = line_index_for_caret_position(caret_position) + 1
|
37
|
+
async_exec {
|
38
|
+
self.line_number = new_line_number
|
39
|
+
}
|
40
|
+
end
|
41
|
+
observe(self, :line_number) do
|
42
|
+
if line_number
|
43
|
+
line_index = line_number - 1
|
44
|
+
new_caret_position = caret_position_for_line_index(line_index)
|
45
|
+
current_caret_position = self.caret_position
|
46
|
+
async_exec {
|
47
|
+
self.caret_position = new_caret_position unless current_caret_position && line_index_for_caret_position(new_caret_position) == line_index_for_caret_position(current_caret_position)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue # in case of a binary file
|
52
|
+
stop_filewatcher
|
38
53
|
end
|
39
|
-
rescue
|
40
|
-
# no op in case of a binary file
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
@@ -45,15 +58,34 @@ module Glimmer
|
|
45
58
|
@path = the_path
|
46
59
|
generate_display_path
|
47
60
|
end
|
48
|
-
|
61
|
+
|
49
62
|
def generate_display_path
|
50
|
-
|
63
|
+
return if @path.empty?
|
64
|
+
@display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
|
51
65
|
end
|
52
66
|
|
67
|
+
def scratchpad?
|
68
|
+
path.to_s.empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
def backup_properties
|
72
|
+
[:find_text, :replace_text, :case_sensitive, :top_pixel, :caret_position, :selection_count].reduce({}) do |hash, property|
|
73
|
+
hash.merge(property => send(property))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def restore_properties(properties_hash)
|
78
|
+
return if properties_hash[:caret_position] == 0 && properties_hash[:selection_count] == 0 && properties_hash[:find_text].nil? && properties_hash[:replace_text].nil? && properties_hash[:top_pixel] == 0 && properties_hash[:case_sensitive].nil?
|
79
|
+
properties_hash.each do |property, value|
|
80
|
+
send("#{property}=", value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
53
84
|
# to use for widget data-binding
|
54
85
|
def content=(value)
|
55
86
|
value = value.gsub("\t", ' ')
|
56
87
|
if dirty_content != value
|
88
|
+
# TODO fix this command recording hack by truly recording every text change as a proper command (add process_key command, paste command, cut command, etc...)
|
57
89
|
Command.do(self) # record a native (OS-widget) operation
|
58
90
|
self.dirty_content = value
|
59
91
|
end
|
@@ -68,7 +100,7 @@ module Glimmer
|
|
68
100
|
self.selection = Point.new(value, value + selection_count.to_i)
|
69
101
|
self.top_pixel = old_top_pixel
|
70
102
|
end
|
71
|
-
|
103
|
+
|
72
104
|
def caret_position
|
73
105
|
selection.x
|
74
106
|
end
|
@@ -76,63 +108,85 @@ module Glimmer
|
|
76
108
|
def selection_count
|
77
109
|
selection.y - selection.x
|
78
110
|
end
|
79
|
-
|
111
|
+
|
80
112
|
def selection_count=(value)
|
81
113
|
self.selection = Point.new(caret_position, caret_position + value.to_i)
|
82
|
-
end
|
83
|
-
|
114
|
+
end
|
115
|
+
|
84
116
|
def name=(the_name)
|
85
|
-
new_path = path.sub(/#{Regexp.escape(@name)}$/, the_name)
|
117
|
+
new_path = path.sub(/#{Regexp.escape(@name)}$/, the_name) unless scratchpad?
|
86
118
|
@name = the_name
|
87
|
-
if ::File.
|
119
|
+
if !scratchpad? && ::File.exist?(path)
|
88
120
|
FileUtils.mv(path, new_path)
|
89
121
|
self.path = new_path
|
90
122
|
end
|
91
123
|
end
|
92
124
|
|
125
|
+
def dirty_content
|
126
|
+
init_content
|
127
|
+
@dirty_content
|
128
|
+
end
|
129
|
+
|
93
130
|
def dirty_content=(the_content)
|
94
|
-
|
131
|
+
# TODO set partial dirty content by line(s) for enhanced performance
|
132
|
+
@dirty_content = the_content
|
133
|
+
old_caret_position = caret_position
|
134
|
+
old_top_pixel = top_pixel
|
95
135
|
notify_observers(:content)
|
136
|
+
if @formatting_dirty_content_for_writing
|
137
|
+
self.caret_position = old_caret_position
|
138
|
+
self.selection_count = 0
|
139
|
+
self.top_pixel = old_top_pixel
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def read_dirty_content
|
144
|
+
path.empty? ? '' : ::File.read(path)
|
96
145
|
end
|
97
|
-
|
146
|
+
|
98
147
|
def start_filewatcher
|
148
|
+
return if scratchpad?
|
99
149
|
@filewatcher = Filewatcher.new(@path)
|
100
|
-
@thread = Thread.new(@filewatcher) do |fw|
|
150
|
+
@thread = Thread.new(@filewatcher) do |fw|
|
101
151
|
fw.watch do |filename, event|
|
102
|
-
|
103
|
-
|
104
|
-
# test read dirty content
|
105
|
-
read_dirty_content.split("\n")
|
106
|
-
async_exec do
|
152
|
+
async_exec do
|
153
|
+
begin
|
107
154
|
self.dirty_content = read_dirty_content if read_dirty_content != dirty_content
|
155
|
+
rescue StandardError, Errno::ENOENT
|
156
|
+
# in case of a binary file
|
157
|
+
stop_filewatcher
|
108
158
|
end
|
109
|
-
rescue
|
110
|
-
# no op in case of a binary file
|
111
159
|
end
|
112
160
|
end
|
113
161
|
end
|
114
162
|
end
|
115
|
-
|
163
|
+
|
116
164
|
def stop_filewatcher
|
117
165
|
@filewatcher&.stop
|
118
166
|
end
|
119
167
|
|
120
|
-
def format_dirty_content_for_writing!
|
121
|
-
new_dirty_content = "#{dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
|
122
|
-
self.dirty_content = new_dirty_content if new_dirty_content != self.dirty_content
|
123
|
-
end
|
124
|
-
|
125
168
|
def write_dirty_content
|
126
|
-
|
169
|
+
# TODO write partial dirty content by line(s) for enhanced performance
|
170
|
+
return if scratchpad? || !::File.exist?(path) || !::File.exists?(path) || read_dirty_content == dirty_content
|
127
171
|
format_dirty_content_for_writing!
|
128
|
-
::File.write(path, dirty_content)
|
129
|
-
rescue => e
|
172
|
+
::File.write(path, dirty_content)
|
173
|
+
rescue StandardError, ArgumentError => e
|
130
174
|
puts "Error in writing dirty content for #{path}"
|
131
175
|
puts e.full_message
|
132
176
|
end
|
133
|
-
|
177
|
+
|
178
|
+
def format_dirty_content_for_writing!
|
179
|
+
new_dirty_content = dirty_content.split("\n").map {|line| line.strip.empty? ? line : line.rstrip }.join("\n")
|
180
|
+
new_dirty_content = "#{new_dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
|
181
|
+
if new_dirty_content != self.dirty_content
|
182
|
+
@formatting_dirty_content_for_writing = true
|
183
|
+
self.dirty_content = new_dirty_content
|
184
|
+
@formatting_dirty_content_for_writing = false
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
134
188
|
def write_raw_dirty_content
|
135
|
-
return
|
189
|
+
return if scratchpad? || !::File.exist?(path)
|
136
190
|
::File.write(path, dirty_content) if ::File.exists?(path)
|
137
191
|
rescue => e
|
138
192
|
puts "Error in writing raw dirty content for #{path}"
|
@@ -143,19 +197,20 @@ module Glimmer
|
|
143
197
|
current_line.to_s.match(/^(\s+)/).to_a[1].to_s
|
144
198
|
end
|
145
199
|
|
146
|
-
def current_line
|
200
|
+
def current_line
|
147
201
|
lines[line_number - 1]
|
148
202
|
end
|
149
203
|
|
150
204
|
def delete!
|
151
|
-
FileUtils.rm(path)
|
205
|
+
FileUtils.rm(path) unless scratchpad?
|
152
206
|
end
|
153
|
-
|
207
|
+
|
154
208
|
def prefix_new_line!
|
155
209
|
the_lines = lines
|
156
210
|
the_lines[line_number-1...line_number-1] = [current_line_indentation]
|
157
211
|
self.dirty_content = the_lines.join("\n")
|
158
212
|
self.caret_position = caret_position_for_line_index(line_number-1) + current_line_indentation.size
|
213
|
+
self.selection_count = 0
|
159
214
|
end
|
160
215
|
|
161
216
|
def insert_new_line!
|
@@ -163,8 +218,9 @@ module Glimmer
|
|
163
218
|
the_lines[line_number...line_number] = [current_line_indentation]
|
164
219
|
self.dirty_content = the_lines.join("\n")
|
165
220
|
self.caret_position = caret_position_for_line_index(line_number) + current_line_indentation.size
|
221
|
+
self.selection_count = 0
|
166
222
|
end
|
167
|
-
|
223
|
+
|
168
224
|
def comment_line!
|
169
225
|
old_lines = lines
|
170
226
|
return if old_lines.size < 1
|
@@ -190,7 +246,7 @@ module Glimmer
|
|
190
246
|
delta += 2
|
191
247
|
end
|
192
248
|
end
|
193
|
-
self.dirty_content = new_lines.join("\n")
|
249
|
+
self.dirty_content = new_lines.join("\n")
|
194
250
|
if old_selection_count.to_i > 0
|
195
251
|
self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
196
252
|
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
@@ -198,9 +254,10 @@ module Glimmer
|
|
198
254
|
new_caret_position = old_caret_position + delta
|
199
255
|
new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
|
200
256
|
self.caret_position = new_caret_position
|
257
|
+
self.selection_count = 0
|
201
258
|
end
|
202
259
|
end
|
203
|
-
|
260
|
+
|
204
261
|
def indent!
|
205
262
|
new_lines = lines
|
206
263
|
old_lines = lines
|
@@ -216,16 +273,16 @@ module Glimmer
|
|
216
273
|
new_lines[the_line_index] = " #{the_line}"
|
217
274
|
end
|
218
275
|
old_caret_position = self.caret_position
|
219
|
-
self.dirty_content = new_lines.join("\n")
|
276
|
+
self.dirty_content = new_lines.join("\n")
|
220
277
|
if old_selection_count.to_i > 0
|
221
|
-
|
222
|
-
|
223
|
-
self.selection = Point.new(caret_position_value, caret_position_value + selection_count_value)
|
278
|
+
self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
279
|
+
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
224
280
|
else
|
225
281
|
self.caret_position = old_caret_position + delta
|
282
|
+
self.selection_count = 0
|
226
283
|
end
|
227
284
|
end
|
228
|
-
|
285
|
+
|
229
286
|
def outdent!
|
230
287
|
new_lines = lines
|
231
288
|
old_lines = lines
|
@@ -246,18 +303,18 @@ module Glimmer
|
|
246
303
|
delta = -1
|
247
304
|
end
|
248
305
|
end
|
249
|
-
self.dirty_content = new_lines.join("\n")
|
306
|
+
self.dirty_content = new_lines.join("\n")
|
250
307
|
if old_selection_count.to_i > 0
|
251
|
-
|
252
|
-
|
253
|
-
self.selection = Point.new(caret_position_value, caret_position_value + selection_count_value)
|
308
|
+
self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
309
|
+
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
254
310
|
else
|
255
311
|
new_caret_position = old_caret_position + delta
|
256
312
|
new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
|
257
313
|
self.caret_position = new_caret_position
|
314
|
+
self.selection_count = 0
|
258
315
|
end
|
259
316
|
end
|
260
|
-
|
317
|
+
|
261
318
|
def kill_line!
|
262
319
|
new_lines = lines
|
263
320
|
return if new_lines.size < 1
|
@@ -270,7 +327,7 @@ module Glimmer
|
|
270
327
|
self.caret_position = caret_position_for_line_index(old_line_index) + [line_position, lines[old_line_index].to_s.size].min
|
271
328
|
self.selection_count = 0
|
272
329
|
end
|
273
|
-
|
330
|
+
|
274
331
|
def duplicate_line!
|
275
332
|
new_lines = lines
|
276
333
|
old_lines = lines
|
@@ -292,11 +349,12 @@ module Glimmer
|
|
292
349
|
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
293
350
|
else
|
294
351
|
self.caret_position = old_caret_position + delta
|
352
|
+
self.selection_count = 0
|
295
353
|
end
|
296
354
|
end
|
297
|
-
|
355
|
+
|
298
356
|
def find_next
|
299
|
-
return if find_text.to_s.empty?
|
357
|
+
return if find_text.to_s.empty?
|
300
358
|
all_lines = lines
|
301
359
|
the_line_index = line_index_for_caret_position(caret_position)
|
302
360
|
line_position = line_position_for_caret_position(caret_position)
|
@@ -312,13 +370,12 @@ module Glimmer
|
|
312
370
|
if occurrence_index
|
313
371
|
self.caret_position = caret_position_for_line_index(the_index) + start_position + occurrence_index
|
314
372
|
self.selection_count = find_text.to_s.size
|
315
|
-
self.selection = Point.new(self.caret_position, self.caret_position + self.selection_count)
|
316
373
|
return
|
317
374
|
end
|
318
375
|
end
|
319
376
|
end
|
320
377
|
end
|
321
|
-
|
378
|
+
|
322
379
|
def find_previous
|
323
380
|
return if find_text.to_s.empty?
|
324
381
|
all_lines = lines
|
@@ -343,7 +400,7 @@ module Glimmer
|
|
343
400
|
end
|
344
401
|
end
|
345
402
|
end
|
346
|
-
|
403
|
+
|
347
404
|
def ensure_find_next
|
348
405
|
return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
|
349
406
|
find_next unless found_text?(self.caret_position)
|
@@ -352,37 +409,45 @@ module Glimmer
|
|
352
409
|
def found_text?(caret_position)
|
353
410
|
dirty_content[caret_position.to_i, find_text.to_s.size].to_s.downcase == find_text.to_s.downcase
|
354
411
|
end
|
355
|
-
|
412
|
+
|
356
413
|
def replace_next!
|
357
414
|
return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
|
358
415
|
ensure_find_next
|
359
416
|
new_dirty_content = dirty_content
|
360
417
|
new_dirty_content[caret_position, find_text.size] = replace_text.to_s
|
361
418
|
self.dirty_content = new_dirty_content
|
362
|
-
find_next
|
419
|
+
find_next
|
363
420
|
find_next if replace_text.to_s.include?(find_text) && !replace_text.to_s.start_with?(find_text)
|
364
421
|
end
|
365
|
-
|
422
|
+
|
366
423
|
def page_up
|
367
424
|
self.selection_count = 0
|
368
425
|
self.line_number = [(self.line_number - 15), 1].max
|
369
426
|
end
|
370
|
-
|
427
|
+
|
371
428
|
def page_down
|
372
429
|
self.selection_count = 0
|
373
430
|
self.line_number = [(self.line_number + 15), lines.size].min
|
374
431
|
end
|
375
|
-
|
432
|
+
|
376
433
|
def home
|
377
434
|
self.selection_count = 0
|
378
435
|
self.line_number = 1
|
379
436
|
end
|
380
|
-
|
437
|
+
|
381
438
|
def end
|
382
439
|
self.selection_count = 0
|
383
440
|
self.line_number = lines.size
|
384
441
|
end
|
385
|
-
|
442
|
+
|
443
|
+
def start_of_line
|
444
|
+
self.caret_position = caret_position_for_line_index(self.line_number - 1)
|
445
|
+
end
|
446
|
+
|
447
|
+
def end_of_line
|
448
|
+
self.caret_position = caret_position_for_line_index(self.line_number) - 1
|
449
|
+
end
|
450
|
+
|
386
451
|
def move_up!
|
387
452
|
old_lines = lines
|
388
453
|
return if old_lines.size < 2
|
@@ -401,7 +466,7 @@ module Glimmer
|
|
401
466
|
self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
|
402
467
|
self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
|
403
468
|
end
|
404
|
-
|
469
|
+
|
405
470
|
def move_down!
|
406
471
|
old_lines = lines
|
407
472
|
return if old_lines.size < 2
|
@@ -420,25 +485,38 @@ module Glimmer
|
|
420
485
|
self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
|
421
486
|
self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
|
422
487
|
end
|
423
|
-
|
488
|
+
|
489
|
+
def run
|
490
|
+
begin
|
491
|
+
if scratchpad?
|
492
|
+
eval content
|
493
|
+
else
|
494
|
+
write_dirty_content
|
495
|
+
load path
|
496
|
+
end
|
497
|
+
rescue SyntaxError, StandardError => e
|
498
|
+
puts e.full_message
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
424
502
|
def lines
|
425
503
|
dirty_content.split("\n")
|
426
504
|
end
|
427
|
-
|
505
|
+
|
428
506
|
def line_for_caret_position(caret_position)
|
429
507
|
lines[line_index_for_caret_position(caret_position.to_i)]
|
430
508
|
end
|
431
|
-
|
509
|
+
|
432
510
|
def line_index_for_caret_position(caret_position)
|
433
511
|
dirty_content[0...caret_position.to_i].count("\n")
|
434
512
|
end
|
435
|
-
|
513
|
+
|
436
514
|
def caret_position_for_line_index(line_index)
|
437
515
|
cp = lines[0...line_index].join("\n").size
|
438
516
|
cp += 1 if line_index > 0
|
439
517
|
cp
|
440
518
|
end
|
441
|
-
|
519
|
+
|
442
520
|
def caret_position_for_caret_position_start_of_line(caret_position)
|
443
521
|
caret_position_for_line_index(line_index_for_caret_position(caret_position))
|
444
522
|
end
|
@@ -449,23 +527,23 @@ module Glimmer
|
|
449
527
|
caret_position = caret_position.to_i
|
450
528
|
caret_position - caret_position_for_caret_position_start_of_line(caret_position)
|
451
529
|
end
|
452
|
-
|
530
|
+
|
453
531
|
def line_caret_positions_for_selection(caret_position, selection_count)
|
454
532
|
line_indices = line_indices_for_selection(caret_position, selection_count)
|
455
533
|
line_caret_positions = line_indices.map { |line_index| caret_position_for_line_index(line_index) }.to_a
|
456
534
|
end
|
457
|
-
|
535
|
+
|
458
536
|
def end_caret_position_line_index(caret_position, selection_count)
|
459
537
|
end_caret_position = caret_position + selection_count.to_i
|
460
538
|
end_caret_position -= 1 if dirty_content[end_caret_position - 1] == "\n"
|
461
539
|
end_line_index = line_index_for_caret_position(end_caret_position)
|
462
540
|
end
|
463
|
-
|
541
|
+
|
464
542
|
def lines_for_selection(caret_position, selection_count)
|
465
543
|
line_indices = line_indices_for_selection(caret_position, selection_count)
|
466
544
|
lines[line_indices.first..line_indices.last]
|
467
545
|
end
|
468
|
-
|
546
|
+
|
469
547
|
def line_indices_for_selection(caret_position, selection_count)
|
470
548
|
start_line_index = line_index_for_caret_position(caret_position)
|
471
549
|
if selection_count.to_i > 0
|
@@ -475,22 +553,22 @@ module Glimmer
|
|
475
553
|
end
|
476
554
|
(start_line_index..end_line_index).to_a
|
477
555
|
end
|
478
|
-
|
556
|
+
|
479
557
|
def children
|
480
558
|
[]
|
481
559
|
end
|
482
|
-
|
560
|
+
|
483
561
|
def to_s
|
484
562
|
path
|
485
563
|
end
|
486
|
-
|
564
|
+
|
487
565
|
def eql?(other)
|
488
566
|
self.path.eql?(other&.path)
|
489
567
|
end
|
490
|
-
|
568
|
+
|
491
569
|
def hash
|
492
570
|
self.path.hash
|
493
571
|
end
|
494
|
-
end
|
572
|
+
end
|
495
573
|
end
|
496
574
|
end
|