glimmer-cs-gladiator 0.8.1 → 0.8.2
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/CHANGELOG.md +12 -0
- data/README.md +7 -10
- data/VERSION +1 -1
- data/glimmer-cs-gladiator.gemspec +4 -3
- data/lib/models/glimmer/gladiator/command.rb +112 -112
- data/lib/models/glimmer/gladiator/file.rb +713 -662
- data/lib/views/glimmer/gladiator.rb +34 -22
- data/lib/views/glimmer/gladiator/file_edit_menu.rb +67 -0
- data/lib/views/glimmer/gladiator/gladiator_menu_bar.rb +33 -6
- data/lib/views/glimmer/gladiator/progress_shell.rb +29 -31
- data/lib/views/glimmer/gladiator/text_editor.rb +9 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c2b457e91ebddf9e822a15d20ba6f78957285126d9ba6df7923bfdfa2cd49c2
|
4
|
+
data.tar.gz: fb1a55aaf1eb98c4a247dc1f8aead7e0c5c6fff76bf64932540cc6b01528e3f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5967f794ade7c65630b0a8dd34e41d066411a710e6c030d7e07a066e7612199e5651dba95f5bd0dffe36d43dfb906c1c6cc2a85ef03060ed771244b8ad85dbd1
|
7
|
+
data.tar.gz: 637a0a898869617472fd13c6cb827d04f316bae498602ae69dbba6eefcee48f7783d1b9aaff5f54a46569c0f0aaa2db40dbee5bf43dd35e0d70bed0bf2c89398
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,19 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.8.2
|
4
|
+
|
5
|
+
- Add a right click menu on text editor area with undo redo | cut, copy, paste, delete | select all
|
6
|
+
- Replaced error message_box with a more readable error dialog for running Ruby code
|
7
|
+
- Update Ruby Run menu item command to run against the top level binding receiver (ensuring no weird errors when including Glimmer)
|
8
|
+
- In app mode, display an "Open Project..." button
|
9
|
+
- In app mode, set gladiator icon on initial shell
|
10
|
+
- Fix full line selection on Windows (SHIFT+HOME or SHIFT+END)
|
11
|
+
- Fix issue with display saving original file before changes when running in app mode, making changes to an open project, and then closing
|
12
|
+
- Fix caret position after formatting dirty content (when pasting a string that has extra empty spaces for example)
|
13
|
+
|
3
14
|
## 0.8.1
|
4
15
|
|
16
|
+
- Package Gladiator as a Windows MSI file
|
5
17
|
- Fix issue with HOME and END taking to beginning of file and end of file on Windows instead of beginning of line and end of line
|
6
18
|
- Fix opening first tab on Windows (shows up as blank, but second tab shows up fine)
|
7
19
|
- Fix issue with losing focus on changing tabs on Windows via Windows default tab switching shortcuts of CTRL+PGUP & CTRL+PGDN
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# <img src='https://raw.githubusercontent.com/AndyObtiva/glimmer-cs-gladiator/master/images/glimmer-cs-gladiator-logo.svg' height=85 /> Gladiator 0.8.
|
1
|
+
# <img src='https://raw.githubusercontent.com/AndyObtiva/glimmer-cs-gladiator/master/images/glimmer-cs-gladiator-logo.svg' height=85 /> Gladiator 0.8.2 - [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
|
|
@@ -119,7 +119,9 @@ Gladiator currently supports the following text editing features (including keyb
|
|
119
119
|
|
120
120
|
[Download Gladiator Mac DMG Installer](https://www.dropbox.com/s/uklftb8q16czgo6/Gladiator-0.8.1.dmg?dl=1)
|
121
121
|
|
122
|
-
[Download Gladiator Windows MSI Installer](https://www.dropbox.com/s/
|
122
|
+
[Download Gladiator Windows MSI Installer](https://www.dropbox.com/s/edvbgsrjbdwc8v8/Gladiator-0.8.2.msi?dl=1)
|
123
|
+
|
124
|
+
The packaged version starts with a dialog asking you what project to open. Gladiator does not fully show up until you have selected a project directory.
|
123
125
|
|
124
126
|
Otherwise, if you prefer a command line version, then follow the Setup Instructions below.
|
125
127
|
|
@@ -147,19 +149,14 @@ Run (`jruby -S bundle` or `bundle` directly if you have [RVM](https://rvm.io/)):
|
|
147
149
|
jruby -S bundle
|
148
150
|
```
|
149
151
|
|
150
|
-
Afterwards, to ensure system wide availablility of the `gladiator` command, run this command in an environment that has JRuby:
|
152
|
+
Afterwards, if you are using [RVM](https://rvm.io/) and want to ensure system wide availablility of the `gladiator` command across Ruby versions, run this command in an environment that has JRuby (not needed without [RVM](https://rvm.io/)):
|
151
153
|
|
152
154
|
```
|
153
155
|
gladiator-setup
|
154
|
-
```
|
155
|
-
|
156
|
-
Finally, start a new terminal session or source .gladiator_source:
|
157
|
-
|
158
|
-
```
|
159
156
|
source ~/.gladiator_source
|
160
157
|
```
|
161
158
|
|
162
|
-
You should be able to run `gladiator` from anywhere now
|
159
|
+
You should be able to run `gladiator` from anywhere now.
|
163
160
|
|
164
161
|
## Usage
|
165
162
|
|
@@ -195,7 +192,7 @@ To reuse Gladiator as a Glimmer Custom Shell inside another Glimmer application,
|
|
195
192
|
following to the application's `Gemfile`:
|
196
193
|
|
197
194
|
```
|
198
|
-
gem 'glimmer-cs-gladiator', '>= 0.8.
|
195
|
+
gem 'glimmer-cs-gladiator', '>= 0.8.2'
|
199
196
|
```
|
200
197
|
|
201
198
|
Run:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.
|
1
|
+
0.8.2
|
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: glimmer-cs-gladiator 0.8.
|
5
|
+
# stub: glimmer-cs-gladiator 0.8.2 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer-cs-gladiator".freeze
|
9
|
-
s.version = "0.8.
|
9
|
+
s.version = "0.8.2"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "2021-02-
|
14
|
+
s.date = "2021-02-08"
|
15
15
|
s.description = "Gladiator (short for Glimmer Editor) is a Glimmer sample project under on-going development. 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. Gladiator is also a personal tool for shaping an editor exactly the way I like. I leave building truly professional text editors to software tooling experts who would hopefully use Glimmer one day.".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.executables = ["glimmer-cs-gladiator".freeze, "gladiator".freeze, "gladiator-setup".freeze]
|
@@ -36,6 +36,7 @@ Gem::Specification.new do |s|
|
|
36
36
|
"lib/models/glimmer/gladiator/dir.rb",
|
37
37
|
"lib/models/glimmer/gladiator/file.rb",
|
38
38
|
"lib/views/glimmer/gladiator.rb",
|
39
|
+
"lib/views/glimmer/gladiator/file_edit_menu.rb",
|
39
40
|
"lib/views/glimmer/gladiator/file_explorer_tree.rb",
|
40
41
|
"lib/views/glimmer/gladiator/file_lookup_list.rb",
|
41
42
|
"lib/views/glimmer/gladiator/gladiator_menu_bar.rb",
|
@@ -1,112 +1,112 @@
|
|
1
|
-
module Glimmer
|
2
|
-
class Gladiator
|
3
|
-
class Command
|
4
|
-
include Glimmer
|
5
|
-
|
6
|
-
class << self
|
7
|
-
include Glimmer
|
8
|
-
|
9
|
-
def command_history
|
10
|
-
@command_history ||= {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def command_history_for(file)
|
14
|
-
# keeping a first command to make redo support work by remembering next command after undoing all
|
15
|
-
command_history[file] ||= [Command.new(file)]
|
16
|
-
end
|
17
|
-
|
18
|
-
def do(file, method = nil, *args, command: nil)
|
19
|
-
if command.nil?
|
20
|
-
command ||= Command.new(file, method, *args)
|
21
|
-
command.previous_command = command_history_for(file).last
|
22
|
-
unless command_history_for(file).last.method == :change_content! && method == :change_content!
|
23
|
-
command_history_for(file).last.next_command = command
|
24
|
-
end
|
25
|
-
command.do
|
26
|
-
command_history_for(file) << command unless command_history_for(file).last.method == :change_content! && method == :change_content!
|
27
|
-
else
|
28
|
-
command_history_for(file) << command
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def undo(file)
|
33
|
-
return if command_history_for(file).size <= 1
|
34
|
-
command = command_history_for(file).pop
|
35
|
-
command&.undo
|
36
|
-
end
|
37
|
-
|
38
|
-
def redo(file)
|
39
|
-
command = command_history_for(file).last
|
40
|
-
command&.redo
|
41
|
-
end
|
42
|
-
|
43
|
-
def clear(file)
|
44
|
-
command_history[file] = [Command.new(file)]
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
attr_accessor :file, :method, :args, :previous_command, :next_command,
|
49
|
-
:file_dirty_content, :file_caret_position, :file_selection_count, :previous_file_dirty_content, :previous_file_caret_position, :previous_file_selection_count
|
50
|
-
|
51
|
-
def initialize(file, method = nil, *args)
|
52
|
-
@file = file
|
53
|
-
@method = method
|
54
|
-
@args = args
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
def native?
|
59
|
-
@method.nil?
|
60
|
-
end
|
61
|
-
|
62
|
-
def do
|
63
|
-
return if native?
|
64
|
-
backup
|
65
|
-
execute
|
66
|
-
end
|
67
|
-
|
68
|
-
def undo
|
69
|
-
return if native?
|
70
|
-
restore
|
71
|
-
end
|
72
|
-
|
73
|
-
def redo
|
74
|
-
return if next_command.nil?# || next_command.native?
|
75
|
-
@file.dirty_content = next_command.file_dirty_content.clone
|
76
|
-
@file.caret_position = next_command.file_caret_position
|
77
|
-
@file.selection_count = next_command.file_selection_count
|
78
|
-
Command.do(next_command.file, command: next_command)
|
79
|
-
end
|
80
|
-
|
81
|
-
def backup
|
82
|
-
@previous_file_dirty_content = @file.dirty_content.clone
|
83
|
-
@previous_file_caret_position = @file.caret_position
|
84
|
-
@previous_file_selection_count = @file.selection_count
|
85
|
-
if @method == :change_content!
|
86
|
-
@previous_file_caret_position = @file.last_caret_position
|
87
|
-
@previous_file_selection_count = @file.last_selection_count
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def restore
|
92
|
-
@file.dirty_content = @previous_file_dirty_content.clone
|
93
|
-
@file.caret_position = @previous_file_caret_position
|
94
|
-
@file.selection_count = @previous_file_selection_count
|
95
|
-
end
|
96
|
-
|
97
|
-
def execute
|
98
|
-
@file.start_command
|
99
|
-
@file.send(@method, *@args)
|
100
|
-
@file.end_command
|
101
|
-
@file_dirty_content = @file.dirty_content.clone
|
102
|
-
@file_caret_position = @file.caret_position
|
103
|
-
@file_selection_count = @file.selection_count
|
104
|
-
if previous_command.method == :change_content! && @method == :change_content!
|
105
|
-
previous_command.file_dirty_content = @file_dirty_content
|
106
|
-
previous_command.file_caret_position = @file_caret_position
|
107
|
-
previous_command.file_selection_count = @file_selection_count
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
1
|
+
module Glimmer
|
2
|
+
class Gladiator
|
3
|
+
class Command
|
4
|
+
include Glimmer
|
5
|
+
|
6
|
+
class << self
|
7
|
+
include Glimmer
|
8
|
+
|
9
|
+
def command_history
|
10
|
+
@command_history ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def command_history_for(file)
|
14
|
+
# keeping a first command to make redo support work by remembering next command after undoing all
|
15
|
+
command_history[file] ||= [Command.new(file)]
|
16
|
+
end
|
17
|
+
|
18
|
+
def do(file, method = nil, *args, command: nil)
|
19
|
+
if command.nil?
|
20
|
+
command ||= Command.new(file, method, *args)
|
21
|
+
command.previous_command = command_history_for(file).last
|
22
|
+
unless command_history_for(file).last.method == :change_content! && method == :change_content!
|
23
|
+
command_history_for(file).last.next_command = command
|
24
|
+
end
|
25
|
+
command.do
|
26
|
+
command_history_for(file) << command unless command_history_for(file).last.method == :change_content! && method == :change_content!
|
27
|
+
else
|
28
|
+
command_history_for(file) << command
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def undo(file)
|
33
|
+
return if command_history_for(file).size <= 1
|
34
|
+
command = command_history_for(file).pop
|
35
|
+
command&.undo
|
36
|
+
end
|
37
|
+
|
38
|
+
def redo(file)
|
39
|
+
command = command_history_for(file).last
|
40
|
+
command&.redo
|
41
|
+
end
|
42
|
+
|
43
|
+
def clear(file)
|
44
|
+
command_history[file] = [Command.new(file)]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_accessor :file, :method, :args, :previous_command, :next_command,
|
49
|
+
:file_dirty_content, :file_caret_position, :file_selection_count, :previous_file_dirty_content, :previous_file_caret_position, :previous_file_selection_count
|
50
|
+
|
51
|
+
def initialize(file, method = nil, *args)
|
52
|
+
@file = file
|
53
|
+
@method = method
|
54
|
+
@args = args
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def native?
|
59
|
+
@method.nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
def do
|
63
|
+
return if native?
|
64
|
+
backup
|
65
|
+
execute
|
66
|
+
end
|
67
|
+
|
68
|
+
def undo
|
69
|
+
return if native?
|
70
|
+
restore
|
71
|
+
end
|
72
|
+
|
73
|
+
def redo
|
74
|
+
return if next_command.nil?# || next_command.native?
|
75
|
+
@file.dirty_content = next_command.file_dirty_content.clone
|
76
|
+
@file.caret_position = next_command.file_caret_position
|
77
|
+
@file.selection_count = next_command.file_selection_count
|
78
|
+
Command.do(next_command.file, command: next_command)
|
79
|
+
end
|
80
|
+
|
81
|
+
def backup
|
82
|
+
@previous_file_dirty_content = @file.dirty_content.clone
|
83
|
+
@previous_file_caret_position = @file.caret_position
|
84
|
+
@previous_file_selection_count = @file.selection_count
|
85
|
+
if @method == :change_content!
|
86
|
+
@previous_file_caret_position = @file.last_caret_position
|
87
|
+
@previous_file_selection_count = @file.last_selection_count
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def restore
|
92
|
+
@file.dirty_content = @previous_file_dirty_content.clone
|
93
|
+
@file.caret_position = @previous_file_caret_position
|
94
|
+
@file.selection_count = @previous_file_selection_count
|
95
|
+
end
|
96
|
+
|
97
|
+
def execute
|
98
|
+
@file.start_command
|
99
|
+
@file.send(@method, *@args)
|
100
|
+
@file.end_command
|
101
|
+
@file_dirty_content = @file.dirty_content.clone
|
102
|
+
@file_caret_position = @file.caret_position
|
103
|
+
@file_selection_count = @file.selection_count
|
104
|
+
if previous_command.method == :change_content! && @method == :change_content!
|
105
|
+
previous_command.file_dirty_content = @file_dirty_content
|
106
|
+
previous_command.file_caret_position = @file_caret_position
|
107
|
+
previous_command.file_selection_count = @file_selection_count
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -1,662 +1,713 @@
|
|
1
|
-
module Glimmer
|
2
|
-
class Gladiator
|
3
|
-
class File
|
4
|
-
include Glimmer
|
5
|
-
|
6
|
-
attr_accessor :line_numbers_content, :line_number, :find_text, :replace_text, :top_pixel, :display_path, :case_sensitive, :caret_position, :selection_count, :last_caret_position, :last_selection_count, :line_position
|
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
|
12
|
-
@command_history = []
|
13
|
-
@name = path.empty? ? 'Scratchpad' : ::File.basename(path)
|
14
|
-
self.path = ::File.expand_path(path) unless path.empty?
|
15
|
-
@top_pixel = 0
|
16
|
-
@caret_position = 0
|
17
|
-
@selection_count = 0
|
18
|
-
@last_selection_count = 0
|
19
|
-
@line_number = 1
|
20
|
-
@init = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
def language
|
24
|
-
# TODO consider using Rouge::Lexer.guess_by_filename instead and perhaps guess_by_source when it fails
|
25
|
-
extension = path.split('.').last if path.to_s.include?('.')
|
26
|
-
return 'ruby' if scratchpad?
|
27
|
-
return 'ruby' if path.to_s.end_with?('Gemfile') || path.to_s.end_with?('Rakefile')
|
28
|
-
return 'ruby' if dirty_content.start_with?('#!/usr/bin/env ruby') || dirty_content.start_with?('#!/usr/bin/env jruby')
|
29
|
-
return 'yaml' if path.to_s.end_with?('Gemfile.lock')
|
30
|
-
return 'shell' if extension.nil? && path.to_s.include?('/bin/')
|
31
|
-
case extension
|
32
|
-
# TODO extract case statement to an external config file
|
33
|
-
when 'rb'
|
34
|
-
'ruby'
|
35
|
-
when 'md', 'markdown'
|
36
|
-
'markdown'
|
37
|
-
when 'js', 'es6'
|
38
|
-
'javascript'
|
39
|
-
when 'json'
|
40
|
-
'json'
|
41
|
-
when 'yaml'
|
42
|
-
'yaml'
|
43
|
-
when 'html'
|
44
|
-
'html'
|
45
|
-
when 'h', 'c'
|
46
|
-
'c'
|
47
|
-
when 'hs'
|
48
|
-
'haskell'
|
49
|
-
when 'gradle'
|
50
|
-
'gradle'
|
51
|
-
when 'cpp'
|
52
|
-
'cpp'
|
53
|
-
when 'css'
|
54
|
-
'css'
|
55
|
-
when 'java'
|
56
|
-
'java'
|
57
|
-
when 'jsp'
|
58
|
-
'jsp'
|
59
|
-
when 'plist'
|
60
|
-
'plist'
|
61
|
-
when 'haml'
|
62
|
-
'haml'
|
63
|
-
when 'xml'
|
64
|
-
'xml'
|
65
|
-
when 'ini'
|
66
|
-
'ini'
|
67
|
-
when 'pl'
|
68
|
-
'perl'
|
69
|
-
when 'tcl'
|
70
|
-
'tcl'
|
71
|
-
when 'sass'
|
72
|
-
'sass'
|
73
|
-
when 'scss'
|
74
|
-
'scss'
|
75
|
-
when 'sql'
|
76
|
-
'sql'
|
77
|
-
when 'sh'
|
78
|
-
'shell'
|
79
|
-
when 'vue'
|
80
|
-
'vue'
|
81
|
-
when 'txt', nil
|
82
|
-
'plain_text'
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def init_content
|
87
|
-
unless @init
|
88
|
-
@init = true
|
89
|
-
begin
|
90
|
-
# test read dirty content
|
91
|
-
observe(self, :dirty_content) do
|
92
|
-
line_count = lines.empty? ? 1 : lines.size
|
93
|
-
lines_text_size = [line_count.to_s.size, 4].max
|
94
|
-
old_top_pixel = top_pixel
|
95
|
-
self.line_numbers_content = line_count.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
|
96
|
-
self.top_pixel = old_top_pixel
|
97
|
-
end
|
98
|
-
the_dirty_content = read_dirty_content
|
99
|
-
the_dirty_content.split("\n") # test that it is not a binary file (crashes to rescue block otherwise)
|
100
|
-
self.dirty_content = the_dirty_content
|
101
|
-
observe(self, :caret_position) do |new_caret_position|
|
102
|
-
update_line_number_from_caret_position(new_caret_position)
|
103
|
-
end
|
104
|
-
observe(self, :line_number) do |new_line_number|
|
105
|
-
line_index = line_number - 1
|
106
|
-
new_caret_position = caret_position_for_line_index(line_index)
|
107
|
-
current_caret_position = caret_position
|
108
|
-
line_index_for_new_caret_position = line_index_for_caret_position(new_caret_position)
|
109
|
-
line_index_for_current_caret_position = line_index_for_caret_position(current_caret_position)
|
110
|
-
self.caret_position = new_caret_position unless (current_caret_position && line_index_for_new_caret_position == line_index_for_current_caret_position)
|
111
|
-
end
|
112
|
-
rescue # in case of a binary file
|
113
|
-
stop_filewatcher
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def update_line_number_from_caret_position(new_caret_position)
|
119
|
-
new_line_number = line_index_for_caret_position(caret_position) + 1
|
120
|
-
current_line_number = line_number
|
121
|
-
unless (current_line_number && current_line_number == new_line_number)
|
122
|
-
self.line_number = new_line_number
|
123
|
-
# TODO check if the following line is needed
|
124
|
-
self.line_position = caret_position - caret_position_for_line_index(line_number - 1) + 1
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def path=(the_path)
|
129
|
-
@path = the_path
|
130
|
-
generate_display_path
|
131
|
-
end
|
132
|
-
|
133
|
-
def generate_display_path
|
134
|
-
return if @path.empty?
|
135
|
-
@display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
|
136
|
-
end
|
137
|
-
|
138
|
-
def name=(the_name)
|
139
|
-
new_path = path.sub(/#{Regexp.escape(@name)}$/, the_name) unless scratchpad?
|
140
|
-
@name = the_name
|
141
|
-
if !scratchpad? && ::File.exist?(path)
|
142
|
-
FileUtils.mv(path, new_path)
|
143
|
-
self.path = new_path
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def scratchpad?
|
148
|
-
path.to_s.empty?
|
149
|
-
end
|
150
|
-
|
151
|
-
def backup_properties
|
152
|
-
[:find_text, :replace_text, :case_sensitive, :top_pixel, :caret_position, :selection_count].reduce({}) do |hash, property|
|
153
|
-
hash.merge(property => send(property))
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def restore_properties(properties_hash)
|
158
|
-
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?
|
159
|
-
properties_hash.each do |property, value|
|
160
|
-
send("#{property}=", value)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def caret_position=(value)
|
165
|
-
@last_caret_position = @caret_position
|
166
|
-
@caret_position = value
|
167
|
-
end
|
168
|
-
|
169
|
-
def selection_count=(value)
|
170
|
-
#@last_selection_count = @selection_count
|
171
|
-
@selection_count = value
|
172
|
-
@last_selection_count = @selection_count
|
173
|
-
end
|
174
|
-
|
175
|
-
def dirty_content
|
176
|
-
init_content
|
177
|
-
@dirty_content
|
178
|
-
end
|
179
|
-
|
180
|
-
def dirty_content=(the_content)
|
181
|
-
# TODO set partial dirty content by line(s) for enhanced performance
|
182
|
-
@dirty_content = the_content
|
183
|
-
old_caret_position = caret_position
|
184
|
-
old_top_pixel = top_pixel
|
185
|
-
|
186
|
-
notify_observers(:content)
|
187
|
-
if @formatting_dirty_content_for_writing
|
188
|
-
self.caret_position = old_caret_position
|
189
|
-
self.top_pixel = old_top_pixel
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def content
|
194
|
-
dirty_content
|
195
|
-
end
|
196
|
-
|
197
|
-
# to use for widget data-binding
|
198
|
-
def content=(value)
|
199
|
-
value = value.gsub("\t", ' ')
|
200
|
-
if dirty_content != value
|
201
|
-
Command.do(self, :change_content!, value)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def change_content!(value)
|
206
|
-
self.dirty_content = value
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
@
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
puts
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
new_dirty_content = dirty_content.to_s.split("\n").map {|line| line.strip.empty? ? line : line.rstrip }.join("\n")
|
268
|
-
new_dirty_content = "#{new_dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
|
269
|
-
if new_dirty_content != self.dirty_content
|
270
|
-
@formatting_dirty_content_for_writing = true
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
self.
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
self.
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
self.
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
self.
|
415
|
-
|
416
|
-
self.
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
the_lines
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
self.
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
start_position = 0
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
end
|
585
|
-
|
586
|
-
def
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
end
|
644
|
-
|
645
|
-
def
|
646
|
-
[]
|
647
|
-
end
|
648
|
-
|
649
|
-
def
|
650
|
-
|
651
|
-
end
|
652
|
-
|
653
|
-
def
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
1
|
+
module Glimmer
|
2
|
+
class Gladiator
|
3
|
+
class File
|
4
|
+
include Glimmer
|
5
|
+
|
6
|
+
attr_accessor :line_numbers_content, :line_number, :find_text, :replace_text, :top_pixel, :display_path, :case_sensitive, :caret_position, :selection_count, :last_caret_position, :last_selection_count, :line_position
|
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
|
12
|
+
@command_history = []
|
13
|
+
@name = path.empty? ? 'Scratchpad' : ::File.basename(path)
|
14
|
+
self.path = ::File.expand_path(path) unless path.empty?
|
15
|
+
@top_pixel = 0
|
16
|
+
@caret_position = 0
|
17
|
+
@selection_count = 0
|
18
|
+
@last_selection_count = 0
|
19
|
+
@line_number = 1
|
20
|
+
@init = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def language
|
24
|
+
# TODO consider using Rouge::Lexer.guess_by_filename instead and perhaps guess_by_source when it fails
|
25
|
+
extension = path.split('.').last if path.to_s.include?('.')
|
26
|
+
return 'ruby' if scratchpad?
|
27
|
+
return 'ruby' if path.to_s.end_with?('Gemfile') || path.to_s.end_with?('Rakefile')
|
28
|
+
return 'ruby' if dirty_content.start_with?('#!/usr/bin/env ruby') || dirty_content.start_with?('#!/usr/bin/env jruby')
|
29
|
+
return 'yaml' if path.to_s.end_with?('Gemfile.lock')
|
30
|
+
return 'shell' if extension.nil? && path.to_s.include?('/bin/')
|
31
|
+
case extension
|
32
|
+
# TODO extract case statement to an external config file
|
33
|
+
when 'rb'
|
34
|
+
'ruby'
|
35
|
+
when 'md', 'markdown'
|
36
|
+
'markdown'
|
37
|
+
when 'js', 'es6'
|
38
|
+
'javascript'
|
39
|
+
when 'json'
|
40
|
+
'json'
|
41
|
+
when 'yaml'
|
42
|
+
'yaml'
|
43
|
+
when 'html'
|
44
|
+
'html'
|
45
|
+
when 'h', 'c'
|
46
|
+
'c'
|
47
|
+
when 'hs'
|
48
|
+
'haskell'
|
49
|
+
when 'gradle'
|
50
|
+
'gradle'
|
51
|
+
when 'cpp'
|
52
|
+
'cpp'
|
53
|
+
when 'css'
|
54
|
+
'css'
|
55
|
+
when 'java'
|
56
|
+
'java'
|
57
|
+
when 'jsp'
|
58
|
+
'jsp'
|
59
|
+
when 'plist'
|
60
|
+
'plist'
|
61
|
+
when 'haml'
|
62
|
+
'haml'
|
63
|
+
when 'xml'
|
64
|
+
'xml'
|
65
|
+
when 'ini'
|
66
|
+
'ini'
|
67
|
+
when 'pl'
|
68
|
+
'perl'
|
69
|
+
when 'tcl'
|
70
|
+
'tcl'
|
71
|
+
when 'sass'
|
72
|
+
'sass'
|
73
|
+
when 'scss'
|
74
|
+
'scss'
|
75
|
+
when 'sql'
|
76
|
+
'sql'
|
77
|
+
when 'sh'
|
78
|
+
'shell'
|
79
|
+
when 'vue'
|
80
|
+
'vue'
|
81
|
+
when 'txt', nil
|
82
|
+
'plain_text'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def init_content
|
87
|
+
unless @init
|
88
|
+
@init = true
|
89
|
+
begin
|
90
|
+
# test read dirty content
|
91
|
+
observe(self, :dirty_content) do
|
92
|
+
line_count = lines.empty? ? 1 : lines.size
|
93
|
+
lines_text_size = [line_count.to_s.size, 4].max
|
94
|
+
old_top_pixel = top_pixel
|
95
|
+
self.line_numbers_content = line_count.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
|
96
|
+
self.top_pixel = old_top_pixel
|
97
|
+
end
|
98
|
+
the_dirty_content = read_dirty_content
|
99
|
+
the_dirty_content.split("\n") # test that it is not a binary file (crashes to rescue block otherwise)
|
100
|
+
self.dirty_content = the_dirty_content
|
101
|
+
observe(self, :caret_position) do |new_caret_position|
|
102
|
+
update_line_number_from_caret_position(new_caret_position)
|
103
|
+
end
|
104
|
+
observe(self, :line_number) do |new_line_number|
|
105
|
+
line_index = line_number - 1
|
106
|
+
new_caret_position = caret_position_for_line_index(line_index)
|
107
|
+
current_caret_position = caret_position
|
108
|
+
line_index_for_new_caret_position = line_index_for_caret_position(new_caret_position)
|
109
|
+
line_index_for_current_caret_position = line_index_for_caret_position(current_caret_position)
|
110
|
+
self.caret_position = new_caret_position unless (current_caret_position && line_index_for_new_caret_position == line_index_for_current_caret_position)
|
111
|
+
end
|
112
|
+
rescue # in case of a binary file
|
113
|
+
stop_filewatcher
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def update_line_number_from_caret_position(new_caret_position)
|
119
|
+
new_line_number = line_index_for_caret_position(caret_position) + 1
|
120
|
+
current_line_number = line_number
|
121
|
+
unless (current_line_number && current_line_number == new_line_number)
|
122
|
+
self.line_number = new_line_number
|
123
|
+
# TODO check if the following line is needed
|
124
|
+
self.line_position = caret_position - caret_position_for_line_index(line_number - 1) + 1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def path=(the_path)
|
129
|
+
@path = the_path
|
130
|
+
generate_display_path
|
131
|
+
end
|
132
|
+
|
133
|
+
def generate_display_path
|
134
|
+
return if @path.empty?
|
135
|
+
@display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
|
136
|
+
end
|
137
|
+
|
138
|
+
def name=(the_name)
|
139
|
+
new_path = path.sub(/#{Regexp.escape(@name)}$/, the_name) unless scratchpad?
|
140
|
+
@name = the_name
|
141
|
+
if !scratchpad? && ::File.exist?(path)
|
142
|
+
FileUtils.mv(path, new_path)
|
143
|
+
self.path = new_path
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def scratchpad?
|
148
|
+
path.to_s.empty?
|
149
|
+
end
|
150
|
+
|
151
|
+
def backup_properties
|
152
|
+
[:find_text, :replace_text, :case_sensitive, :top_pixel, :caret_position, :selection_count].reduce({}) do |hash, property|
|
153
|
+
hash.merge(property => send(property))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def restore_properties(properties_hash)
|
158
|
+
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?
|
159
|
+
properties_hash.each do |property, value|
|
160
|
+
send("#{property}=", value)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def caret_position=(value)
|
165
|
+
@last_caret_position = @caret_position
|
166
|
+
@caret_position = value
|
167
|
+
end
|
168
|
+
|
169
|
+
def selection_count=(value)
|
170
|
+
#@last_selection_count = @selection_count
|
171
|
+
@selection_count = value
|
172
|
+
@last_selection_count = @selection_count
|
173
|
+
end
|
174
|
+
|
175
|
+
def dirty_content
|
176
|
+
init_content
|
177
|
+
@dirty_content
|
178
|
+
end
|
179
|
+
|
180
|
+
def dirty_content=(the_content)
|
181
|
+
# TODO set partial dirty content by line(s) for enhanced performance
|
182
|
+
@dirty_content = the_content
|
183
|
+
old_caret_position = caret_position
|
184
|
+
old_top_pixel = top_pixel
|
185
|
+
|
186
|
+
notify_observers(:content)
|
187
|
+
if @formatting_dirty_content_for_writing
|
188
|
+
self.caret_position = old_caret_position
|
189
|
+
self.top_pixel = old_top_pixel
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def content
|
194
|
+
dirty_content
|
195
|
+
end
|
196
|
+
|
197
|
+
# to use for widget data-binding
|
198
|
+
def content=(value)
|
199
|
+
value = value.gsub("\t", ' ')
|
200
|
+
if dirty_content != value
|
201
|
+
Command.do(self, :change_content!, value)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def change_content!(value)
|
206
|
+
self.dirty_content = value
|
207
|
+
format_dirty_content_for_writing!(force: true) if value.to_s.include?("\r\n")
|
208
|
+
update_line_number_from_caret_position(caret_position)
|
209
|
+
end
|
210
|
+
|
211
|
+
def start_command
|
212
|
+
@commmand_in_progress = true
|
213
|
+
end
|
214
|
+
|
215
|
+
def end_command
|
216
|
+
@commmand_in_progress = false
|
217
|
+
end
|
218
|
+
|
219
|
+
def command_in_progress?
|
220
|
+
@commmand_in_progress
|
221
|
+
end
|
222
|
+
|
223
|
+
def close
|
224
|
+
stop_filewatcher
|
225
|
+
remove_all_observers
|
226
|
+
initialize(path, project_dir)
|
227
|
+
Command.clear(self)
|
228
|
+
end
|
229
|
+
|
230
|
+
def read_dirty_content
|
231
|
+
path.empty? ? '' : ::File.read(path)
|
232
|
+
end
|
233
|
+
|
234
|
+
def start_filewatcher
|
235
|
+
return if scratchpad?
|
236
|
+
@filewatcher = Filewatcher.new(@path)
|
237
|
+
@thread = Thread.new(@filewatcher) do |fw|
|
238
|
+
fw.watch do |filename, event|
|
239
|
+
async_exec do
|
240
|
+
begin
|
241
|
+
self.dirty_content = read_dirty_content if read_dirty_content != dirty_content
|
242
|
+
rescue StandardError, Errno::ENOENT
|
243
|
+
# in case of a binary file
|
244
|
+
stop_filewatcher
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def stop_filewatcher
|
252
|
+
@filewatcher&.stop
|
253
|
+
end
|
254
|
+
|
255
|
+
def write_dirty_content
|
256
|
+
# TODO write partial dirty content by line(s) for enhanced performance
|
257
|
+
return if scratchpad? || !::File.exist?(path) || !::File.exists?(path) || read_dirty_content == dirty_content
|
258
|
+
format_dirty_content_for_writing!
|
259
|
+
::File.write(path, dirty_content)
|
260
|
+
rescue StandardError, ArgumentError => e
|
261
|
+
puts "Error in writing dirty content for #{path}"
|
262
|
+
puts e.full_message
|
263
|
+
end
|
264
|
+
|
265
|
+
def format_dirty_content_for_writing!(force: false)
|
266
|
+
return if !force && @commmand_in_progress
|
267
|
+
new_dirty_content = dirty_content.to_s.split("\n").map {|line| line.strip.empty? ? line : line.rstrip }.join("\n")
|
268
|
+
new_dirty_content = "#{new_dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
|
269
|
+
if new_dirty_content != self.dirty_content
|
270
|
+
@formatting_dirty_content_for_writing = true
|
271
|
+
caret_position_diff = dirty_content.to_s.size - new_dirty_content.size
|
272
|
+
self.dirty_content = new_dirty_content
|
273
|
+
self.caret_position = caret_position - caret_position_diff
|
274
|
+
@formatting_dirty_content_for_writing = false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def write_raw_dirty_content
|
279
|
+
return if scratchpad? || !::File.exist?(path)
|
280
|
+
::File.write(path, dirty_content) if ::File.exists?(path)
|
281
|
+
rescue => e
|
282
|
+
puts "Error in writing raw dirty content for #{path}"
|
283
|
+
puts e.full_message
|
284
|
+
end
|
285
|
+
|
286
|
+
def current_line_indentation
|
287
|
+
current_line.to_s.match(/^(\s+)/).to_a[1].to_s
|
288
|
+
end
|
289
|
+
|
290
|
+
def current_line
|
291
|
+
lines[line_number - 1]
|
292
|
+
end
|
293
|
+
|
294
|
+
def delete!
|
295
|
+
FileUtils.rm(path) unless scratchpad?
|
296
|
+
end
|
297
|
+
|
298
|
+
def prefix_new_line!
|
299
|
+
the_lines = lines
|
300
|
+
the_lines[line_number-1...line_number-1] = [current_line_indentation]
|
301
|
+
self.dirty_content = the_lines.join("\n")
|
302
|
+
self.caret_position = caret_position_for_line_index(line_number-1) + current_line_indentation.size
|
303
|
+
self.selection_count = 0
|
304
|
+
end
|
305
|
+
|
306
|
+
def insert_new_line!
|
307
|
+
the_lines = lines
|
308
|
+
the_lines[line_number...line_number] = [current_line_indentation]
|
309
|
+
self.dirty_content = the_lines.join("\n")
|
310
|
+
self.caret_position = caret_position_for_line_index(line_number) + current_line_indentation.size
|
311
|
+
self.selection_count = 0
|
312
|
+
end
|
313
|
+
|
314
|
+
def comment_line!
|
315
|
+
old_lines = lines
|
316
|
+
return if old_lines.size < 1
|
317
|
+
old_selection_count = self.selection_count
|
318
|
+
old_caret_position = self.caret_position
|
319
|
+
old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
|
320
|
+
old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
321
|
+
old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
|
322
|
+
new_lines = lines
|
323
|
+
delta = 0
|
324
|
+
line_indices_for_selection(caret_position, selection_count).reverse.each do | the_line_index |
|
325
|
+
delta = 0
|
326
|
+
the_line = old_lines[the_line_index]
|
327
|
+
return if the_line.nil?
|
328
|
+
if the_line.strip.start_with?('# ')
|
329
|
+
new_lines[the_line_index] = the_line.sub(/# /, '')
|
330
|
+
delta -= 2
|
331
|
+
elsif the_line.strip.start_with?('#')
|
332
|
+
new_lines[the_line_index] = the_line.sub(/#/, '')
|
333
|
+
delta -= 1
|
334
|
+
else
|
335
|
+
new_lines[the_line_index] = "# #{the_line}"
|
336
|
+
delta += 2
|
337
|
+
end
|
338
|
+
end
|
339
|
+
self.dirty_content = new_lines.join("\n")
|
340
|
+
if old_selection_count.to_i > 0
|
341
|
+
self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
342
|
+
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
343
|
+
else
|
344
|
+
new_caret_position = old_caret_position + delta
|
345
|
+
new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
|
346
|
+
self.caret_position = new_caret_position
|
347
|
+
self.selection_count = 0
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def indent!
|
352
|
+
new_lines = lines
|
353
|
+
old_lines = lines
|
354
|
+
return if old_lines.size < 1
|
355
|
+
old_selection_count = self.selection_count
|
356
|
+
old_caret_position = self.caret_position
|
357
|
+
old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
|
358
|
+
old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
359
|
+
old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
|
360
|
+
delta = 2
|
361
|
+
line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
|
362
|
+
the_line = old_lines[the_line_index]
|
363
|
+
new_lines[the_line_index] = " #{the_line}"
|
364
|
+
end
|
365
|
+
old_caret_position = self.caret_position
|
366
|
+
self.dirty_content = new_lines.join("\n")
|
367
|
+
if old_selection_count.to_i > 0
|
368
|
+
self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
369
|
+
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
370
|
+
else
|
371
|
+
self.caret_position = old_caret_position + delta
|
372
|
+
self.selection_count = 0
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def outdent!
|
377
|
+
new_lines = lines
|
378
|
+
old_lines = lines
|
379
|
+
return if old_lines.size < 1
|
380
|
+
old_selection_count = self.selection_count
|
381
|
+
old_caret_position = self.caret_position
|
382
|
+
old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
|
383
|
+
old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
384
|
+
old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
|
385
|
+
delta = 0
|
386
|
+
line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
|
387
|
+
the_line = old_lines[the_line_index]
|
388
|
+
if the_line.to_s.start_with?(' ')
|
389
|
+
new_lines[the_line_index] = the_line.sub(/ /, '')
|
390
|
+
delta = -2
|
391
|
+
elsif the_line&.start_with?(' ')
|
392
|
+
new_lines[the_line_index] = the_line.sub(/ /, '')
|
393
|
+
delta = -1
|
394
|
+
end
|
395
|
+
end
|
396
|
+
self.dirty_content = new_lines.join("\n")
|
397
|
+
if old_selection_count.to_i > 0
|
398
|
+
self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
399
|
+
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
400
|
+
else
|
401
|
+
new_caret_position = old_caret_position + delta
|
402
|
+
new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
|
403
|
+
self.caret_position = new_caret_position
|
404
|
+
self.selection_count = 0
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def kill_line!
|
409
|
+
new_lines = lines
|
410
|
+
return if new_lines.size < 1
|
411
|
+
line_indices = line_indices_for_selection(caret_position, selection_count)
|
412
|
+
new_lines = new_lines[0...line_indices.first] + new_lines[(line_indices.last+1)...new_lines.size]
|
413
|
+
old_caret_position = self.caret_position
|
414
|
+
old_line_index = self.line_number - 1
|
415
|
+
line_position = line_position_for_caret_position(old_caret_position)
|
416
|
+
self.dirty_content = "#{new_lines.join("\n")}\n"
|
417
|
+
self.caret_position = caret_position_for_line_index(old_line_index) + [line_position, lines[old_line_index].to_s.size].min
|
418
|
+
self.selection_count = 0
|
419
|
+
end
|
420
|
+
|
421
|
+
def duplicate_line!
|
422
|
+
new_lines = lines
|
423
|
+
old_lines = lines
|
424
|
+
return if old_lines.size < 1
|
425
|
+
old_selection_count = self.selection_count
|
426
|
+
old_caret_position = self.caret_position
|
427
|
+
old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
|
428
|
+
old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
|
429
|
+
old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
|
430
|
+
the_line_indices = line_indices_for_selection(caret_position, selection_count)
|
431
|
+
the_lines = lines_for_selection(caret_position, selection_count)
|
432
|
+
delta = the_lines.join("\n").size + 1
|
433
|
+
the_lines.each_with_index do |the_line, i|
|
434
|
+
new_lines.insert(the_line_indices.first + i, the_line)
|
435
|
+
end
|
436
|
+
self.dirty_content = new_lines.join("\n")
|
437
|
+
if old_selection_count.to_i > 0
|
438
|
+
self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
|
439
|
+
self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
|
440
|
+
else
|
441
|
+
self.caret_position = old_caret_position + delta
|
442
|
+
self.selection_count = 0
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
def find_next
|
447
|
+
return if find_text.to_s.empty?
|
448
|
+
all_lines = lines
|
449
|
+
the_line_index = line_index_for_caret_position(caret_position)
|
450
|
+
line_position = line_position_for_caret_position(caret_position)
|
451
|
+
found = found_text?(caret_position)
|
452
|
+
2.times do |i|
|
453
|
+
rotation = the_line_index
|
454
|
+
all_lines.rotate(rotation).each_with_index do |the_line, the_index|
|
455
|
+
the_index = (the_index + rotation)%all_lines.size
|
456
|
+
start_position = 0
|
457
|
+
start_position = line_position + find_text.to_s.size if i == 0 && the_index == the_line_index && found_text?(caret_position)
|
458
|
+
text_to_find_in = the_line[start_position..-1]
|
459
|
+
occurrence_index = case_sensitive ? text_to_find_in&.index(find_text.to_s) : text_to_find_in&.downcase&.index(find_text.to_s.downcase)
|
460
|
+
if occurrence_index
|
461
|
+
self.caret_position = caret_position_for_line_index(the_index) + start_position + occurrence_index
|
462
|
+
self.selection_count = find_text.to_s.size
|
463
|
+
return
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def find_previous
|
470
|
+
return if find_text.to_s.empty?
|
471
|
+
all_lines = lines
|
472
|
+
the_line_index = line_index_for_caret_position(caret_position)
|
473
|
+
line_position = line_position_for_caret_position(caret_position)
|
474
|
+
2.times do |i|
|
475
|
+
rotation = - the_line_index - 1 + all_lines.size
|
476
|
+
all_lines.reverse.rotate(rotation).each_with_index do |the_line, the_index|
|
477
|
+
the_index = all_lines.size - 1 - (the_index + rotation)%all_lines.size
|
478
|
+
if the_index == the_line_index
|
479
|
+
start_position = i > 0 ? 0 : (the_line.size - line_position)
|
480
|
+
else
|
481
|
+
start_position = 0
|
482
|
+
end
|
483
|
+
text_to_find_in = the_line.downcase.reverse[start_position...the_line.size].to_s
|
484
|
+
occurrence_index = text_to_find_in.index(find_text.to_s.downcase.reverse)
|
485
|
+
if occurrence_index
|
486
|
+
self.caret_position = caret_position_for_line_index(the_index) + (the_line.size - (start_position + occurrence_index + find_text.to_s.size))
|
487
|
+
self.selection_count = find_text.to_s.size
|
488
|
+
return
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
def ensure_find_next
|
495
|
+
return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
|
496
|
+
find_next unless found_text?(self.caret_position)
|
497
|
+
end
|
498
|
+
|
499
|
+
def found_text?(caret_position)
|
500
|
+
dirty_content[caret_position.to_i, find_text.to_s.size].to_s.downcase == find_text.to_s.downcase
|
501
|
+
end
|
502
|
+
|
503
|
+
def replace_next!
|
504
|
+
return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
|
505
|
+
ensure_find_next
|
506
|
+
new_dirty_content = dirty_content
|
507
|
+
new_dirty_content[caret_position, find_text.size] = replace_text.to_s
|
508
|
+
self.dirty_content = new_dirty_content
|
509
|
+
find_next
|
510
|
+
find_next if replace_text.to_s.include?(find_text) && !replace_text.to_s.start_with?(find_text)
|
511
|
+
end
|
512
|
+
|
513
|
+
def page_up
|
514
|
+
self.selection_count = 0
|
515
|
+
self.line_number = [(self.line_number - 15), 1].max
|
516
|
+
end
|
517
|
+
|
518
|
+
def page_down
|
519
|
+
self.selection_count = 0
|
520
|
+
self.line_number = [(self.line_number + 15), lines.size].min
|
521
|
+
end
|
522
|
+
|
523
|
+
def home
|
524
|
+
self.line_number = 1
|
525
|
+
self.selection_count = 0
|
526
|
+
end
|
527
|
+
|
528
|
+
def end
|
529
|
+
self.line_number = lines.size
|
530
|
+
self.selection_count = 0
|
531
|
+
end
|
532
|
+
|
533
|
+
def start_of_line
|
534
|
+
self.caret_position = caret_position_for_line_index(self.line_number - 1)
|
535
|
+
self.selection_count = 0
|
536
|
+
end
|
537
|
+
|
538
|
+
def end_of_line
|
539
|
+
self.caret_position = caret_position_for_line_index(self.line_number) - 1
|
540
|
+
self.selection_count = 0
|
541
|
+
end
|
542
|
+
|
543
|
+
def select_to_start_of_line
|
544
|
+
old_caret_position = caret_position
|
545
|
+
self.caret_position = caret_position_for_line_index(self.line_number - 1)
|
546
|
+
self.selection_count = old_caret_position - caret_position
|
547
|
+
end
|
548
|
+
|
549
|
+
def select_to_end_of_line
|
550
|
+
self.caret_position = selection_count == 0 ? caret_position : caret_position + selection_count
|
551
|
+
self.selection_count = (caret_position_for_line_index(self.line_number) - 1) - caret_position
|
552
|
+
end
|
553
|
+
|
554
|
+
def delete!
|
555
|
+
new_dirty_content = dirty_content
|
556
|
+
new_dirty_content[caret_position...(caret_position + selection_count)] = ''
|
557
|
+
old_caret_position = caret_position
|
558
|
+
self.dirty_content = new_dirty_content
|
559
|
+
self.caret_position = old_caret_position
|
560
|
+
end
|
561
|
+
|
562
|
+
def cut!
|
563
|
+
Clipboard.copy(selected_text)
|
564
|
+
delete!
|
565
|
+
end
|
566
|
+
|
567
|
+
def copy
|
568
|
+
Clipboard.copy(selected_text)
|
569
|
+
end
|
570
|
+
|
571
|
+
def paste!
|
572
|
+
new_dirty_content = dirty_content
|
573
|
+
pasted_text = Clipboard.paste
|
574
|
+
new_dirty_content[caret_position...(caret_position + selection_count)] = pasted_text
|
575
|
+
old_caret_position = caret_position
|
576
|
+
self.dirty_content = new_dirty_content
|
577
|
+
self.caret_position = old_caret_position + pasted_text.to_s.size
|
578
|
+
self.selection_count = 0
|
579
|
+
end
|
580
|
+
|
581
|
+
def select_all
|
582
|
+
self.caret_position = 0
|
583
|
+
self.selection_count = dirty_content.to_s.size
|
584
|
+
end
|
585
|
+
|
586
|
+
def selected_text
|
587
|
+
dirty_content.to_s[caret_position, selection_count]
|
588
|
+
end
|
589
|
+
|
590
|
+
def move_up!
|
591
|
+
old_lines = lines
|
592
|
+
return if old_lines.size < 2
|
593
|
+
old_selection_count = self.selection_count
|
594
|
+
old_caret_position = self.caret_position
|
595
|
+
old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
|
596
|
+
old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
|
597
|
+
old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
|
598
|
+
new_lines = lines
|
599
|
+
the_line_indices = line_indices_for_selection(caret_position, selection_count)
|
600
|
+
the_lines = lines_for_selection(caret_position, selection_count)
|
601
|
+
new_line_index = [the_line_indices.first - 1, 0].max
|
602
|
+
new_lines[the_line_indices.first..the_line_indices.last] = []
|
603
|
+
new_lines[new_line_index...new_line_index] = the_lines
|
604
|
+
self.dirty_content = new_lines.join("\n")
|
605
|
+
self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
|
606
|
+
self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
|
607
|
+
end
|
608
|
+
|
609
|
+
def move_down!
|
610
|
+
old_lines = lines
|
611
|
+
return if old_lines.size < 2
|
612
|
+
old_selection_count = self.selection_count
|
613
|
+
old_caret_position = self.caret_position
|
614
|
+
old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
|
615
|
+
old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
|
616
|
+
old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
|
617
|
+
new_lines = lines
|
618
|
+
the_line_indices = line_indices_for_selection(caret_position, selection_count)
|
619
|
+
the_lines = lines_for_selection(caret_position, selection_count)
|
620
|
+
new_line_index = [the_line_indices.first + 1, new_lines.size - 1].min
|
621
|
+
new_lines[the_line_indices.first..the_line_indices.last] = []
|
622
|
+
new_lines[new_line_index...new_line_index] = the_lines
|
623
|
+
self.dirty_content = new_lines.join("\n")
|
624
|
+
self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
|
625
|
+
self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
|
626
|
+
end
|
627
|
+
|
628
|
+
def run
|
629
|
+
if scratchpad?
|
630
|
+
TOPLEVEL_BINDING.receiver.send(:eval, content)
|
631
|
+
else
|
632
|
+
write_dirty_content
|
633
|
+
load path
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
def lines
|
638
|
+
need_padding = dirty_content.to_s.end_with?("\n")
|
639
|
+
splittable_content = need_padding ? "#{dirty_content} " : dirty_content
|
640
|
+
the_lines = splittable_content.split("\n")
|
641
|
+
the_lines[-1] = the_lines[-1].strip if need_padding
|
642
|
+
the_lines
|
643
|
+
end
|
644
|
+
|
645
|
+
def line_for_caret_position(caret_position)
|
646
|
+
lines[line_index_for_caret_position(caret_position.to_i)]
|
647
|
+
end
|
648
|
+
|
649
|
+
def line_index_for_caret_position(caret_position)
|
650
|
+
dirty_content[0...caret_position.to_i].count("\n")
|
651
|
+
end
|
652
|
+
|
653
|
+
def caret_position_for_line_index(line_index)
|
654
|
+
cp = lines[0...line_index].join("\n").size
|
655
|
+
cp += 1 if line_index > 0
|
656
|
+
cp
|
657
|
+
end
|
658
|
+
|
659
|
+
def caret_position_for_caret_position_start_of_line(caret_position)
|
660
|
+
caret_position_for_line_index(line_index_for_caret_position(caret_position))
|
661
|
+
end
|
662
|
+
|
663
|
+
# position within line containing "caret position" (e.g. for caret position 5 in 1st line, they match as 5, for 15 in line 2 with line 1 having 10 characters, line position is 4)
|
664
|
+
# TODO consider renaming to line_character_position_for_caret_position
|
665
|
+
def line_position_for_caret_position(caret_position)
|
666
|
+
caret_position = caret_position.to_i
|
667
|
+
caret_position - caret_position_for_caret_position_start_of_line(caret_position)
|
668
|
+
end
|
669
|
+
|
670
|
+
def line_caret_positions_for_selection(caret_position, selection_count)
|
671
|
+
line_indices = line_indices_for_selection(caret_position, selection_count)
|
672
|
+
line_caret_positions = line_indices.map { |line_index| caret_position_for_line_index(line_index) }.to_a
|
673
|
+
end
|
674
|
+
|
675
|
+
def end_caret_position_line_index(caret_position, selection_count)
|
676
|
+
end_caret_position = caret_position + selection_count.to_i
|
677
|
+
end_caret_position -= 1 if dirty_content[end_caret_position - 1] == "\n"
|
678
|
+
end_line_index = line_index_for_caret_position(end_caret_position)
|
679
|
+
end
|
680
|
+
|
681
|
+
def lines_for_selection(caret_position, selection_count)
|
682
|
+
line_indices = line_indices_for_selection(caret_position, selection_count)
|
683
|
+
lines[line_indices.first..line_indices.last]
|
684
|
+
end
|
685
|
+
|
686
|
+
def line_indices_for_selection(caret_position, selection_count)
|
687
|
+
start_line_index = line_index_for_caret_position(caret_position)
|
688
|
+
if selection_count.to_i > 0
|
689
|
+
end_line_index = end_caret_position_line_index(caret_position, selection_count)
|
690
|
+
else
|
691
|
+
end_line_index = start_line_index
|
692
|
+
end
|
693
|
+
(start_line_index..end_line_index).to_a
|
694
|
+
end
|
695
|
+
|
696
|
+
def children
|
697
|
+
[]
|
698
|
+
end
|
699
|
+
|
700
|
+
def to_s
|
701
|
+
path
|
702
|
+
end
|
703
|
+
|
704
|
+
def eql?(other)
|
705
|
+
self.path.eql?(other&.path)
|
706
|
+
end
|
707
|
+
|
708
|
+
def hash
|
709
|
+
self.path.hash
|
710
|
+
end
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|