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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22778fafc962d8b91e8901b2a70ca645b5cfd5a3dbcf089efa688aecaf643924
4
- data.tar.gz: '0190bf73da9810409ef4cd6f392847c9186761b8e3e71d8094e0e82becfe4600'
3
+ metadata.gz: d31e7dd5189317eaeb3a91a175a050ffaaeb4e82d6c8709167477d26dd7bf237
4
+ data.tar.gz: 370a540665a9d7f3d08154f1e98530521f72e9a720f35fa83f982747e2e5bf5e
5
5
  SHA512:
6
- metadata.gz: 513bedaec5b4d2ec4285b8ab32663099207dc7638588b31d3007b6acf87dcb5c1b03d276d9900f485ea1da1fcd2a3307e0463331caa5e3a87efe00c63632f17d
7
- data.tar.gz: f0fec7f0f3183602e7c287f7edb777b367df756ac76e6e095091821328cc7891623b03f8b6428c24502f8fc90e6aefe5a8b591029c42b2a569a3e115dc037ee8
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 - [Ugliest Text Editor Ever](https://www.reddit.com/r/ruby/comments/hgve8k/gladiator_glimmer_editor_ugliest_text_editor_ever/)
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
  [![Gem Version](https://badge.fury.io/rb/glimmer-cs-gladiator.svg)](http://badge.fury.io/rb/glimmer-cs-gladiator)
4
4
 
5
5
  ![Gladiator](images/glimmer-gladiator.png)
6
6
 
7
- (Now, slightly less ugly with Ruby syntax highlighting colors)
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
- - Drag and Drop Text Editor Split Screen (drag a file from File Tree or File Lookup List, and it splits the screen)
36
- - Run current Ruby file via Run Menu (CMD+SHIFT+R)
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 very bad handicaps on Linux (performing text editing operations causes scroll jitter)
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.1'
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 file
137
- - Caret position
138
- - Top line position
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
1
+ 0.5.4
@@ -2,4 +2,5 @@ require_relative '../lib/glimmer-cs-gladiator'
2
2
 
3
3
  include Glimmer
4
4
 
5
- gladiator.open
5
+ local_dir = ENV['LOCAL_DIR'] || '.'
6
+ gladiator(project_dir_path: local_dir).open
@@ -23,7 +23,7 @@ module Glimmer
23
23
  args = SWTProxy.constantify_args(args)
24
24
  @swt_layout = self.class.swt_layout_class_for(underscored_layout_name).new(*args)
25
25
  @widget_proxy.swt_widget.setLayout(@swt_layout)
26
- end
26
+ end
27
27
  end
28
28
  end
29
29
  end
@@ -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
- REFRESH_DELAY = 7
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
- class << self
11
- def local_dir
12
- unless @local_dir
13
- @local_dir = new(ENV['LOCAL_DIR'] || '.', true)
14
- # @local_dir.refresh
15
- @filewatcher = Filewatcher.new(@local_dir.path)
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
- if @last_update.nil? || (Time.now.to_f - @last_update) > REFRESH_DELAY
19
- @local_dir.refresh if !filename.include?('new_file') && !@local_dir.selected_child_path_history.include?(filename) && filename != @local_dir.selected_child_path
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
- @local_dir
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(Dir.local_dir.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
- Dir.local_dir.ignore_paths.reduce(false) do |result, ignore_path|
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) ? Gladiator::File.new(p) : Gladiator::Dir.new(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(Dir.local_dir.path, '')
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
- full_selected_path = selected_path.include?(Dir.local_dir.path) ? selected_path : ::File.join(Dir.local_dir.path, selected_path)
145
- return if selected_path.nil? ||
146
- ::Dir.exist?(full_selected_path) ||
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 = Gladiator::File.new(selected_path)
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
- def initialize(path)
12
- raise "Not a file path: #{path}" unless ::File.file?(path)
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
- read_dirty_content = ::File.read(path)
20
- begin
21
- # test read dirty content
22
- read_dirty_content.split("\n")
23
- observe(self, :dirty_content) do
24
- lines_text_size = lines.size.to_s.size
25
- self.line_numbers_content = lines.size.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
26
- end
27
- @line_number = 1
28
- self.dirty_content = read_dirty_content
29
- observe(self, :selection) do
30
- self.line_number = line_index_for_caret_position(caret_position) + 1
31
- end
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
- @display_path = @path.sub(Dir.local_dir.path, '').sub(/^\//, '')
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.exists?(path)
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
- @dirty_content = the_content if ::File.exist?(path)
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
- begin
103
- read_dirty_content = ::File.read(path)
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
- return unless ::File.exist?(path)
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) if ::File.exists?(path)
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 unless ::File.exist?(path)
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
- caret_position_value = caret_position_for_line_index(old_caret_position_line_index)
222
- selection_count_value = (caret_position_for_line_index(old_end_caret_line_index + 1) - caret_position_value)
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
- caret_position_value = caret_position_for_line_index(old_caret_position_line_index)
252
- selection_count_value = (caret_position_for_line_index(old_end_caret_line_index + 1) - caret_position_value)
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