glimmer-cs-gladiator 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/glimmer-cs-gladiator.svg)](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
|