textbringer 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +14 -0
- data/exe/textbringer +20 -6
- data/lib/textbringer/buffer.rb +49 -0
- data/lib/textbringer/commands.rb +9 -15
- data/lib/textbringer/config.rb +1 -0
- data/lib/textbringer/controller.rb +4 -4
- data/lib/textbringer/keymap.rb +6 -11
- data/lib/textbringer/modes/programming_mode.rb +3 -0
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +160 -95
- data/textbringer.gemspec +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df53610e7d9e7a4e7b657d38d9366a3be4752231
|
4
|
+
data.tar.gz: 07ce8c686e767f2d893f44ccb6994b8e6690abf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64d0421dec0fbab6af6fcb2e9c60497c959fb2d9d1bed99a74c764a8e4ac2f8622e468c452bfec492757967cb7943010529b5da2daad4866fe974cad3e67dfd3
|
7
|
+
data.tar.gz: f9f5ba83a29a959143b37d4388174828bd9414a0f2ddaef7d094075b1b8b314a8d7f708565aaf791a3d59da537eeb0bea685b57c31e40db0776fabbf9952d7ab
|
data/CHANGES.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
## 0.1.2
|
2
|
+
|
3
|
+
* Use curses instead of ncursesw.
|
4
|
+
* Support Windows.
|
5
|
+
* Dump unsaved buffers to ~/.textbringer/buffer_dump when textbringer crashes.
|
6
|
+
* Many bug fixes.
|
7
|
+
|
8
|
+
## 0.1.1
|
9
|
+
|
10
|
+
* Rename exe/tb to exe/textbringer to avoid conflict with akr/tb.
|
11
|
+
|
12
|
+
## 0.1.0
|
13
|
+
|
14
|
+
* First version
|
data/exe/textbringer
CHANGED
@@ -17,7 +17,6 @@ $VERBOSE = nil
|
|
17
17
|
|
18
18
|
Controller.current = Controller.new
|
19
19
|
Window.start do
|
20
|
-
message("Type C-x C-c to exit Textbringer")
|
21
20
|
begin
|
22
21
|
load_user_config
|
23
22
|
ruby_mode
|
@@ -26,15 +25,30 @@ Window.start do
|
|
26
25
|
find_file(arg)
|
27
26
|
end
|
28
27
|
end
|
28
|
+
if Buffer.dumped_buffers_exist?(CONFIG[:buffer_dump_dir])
|
29
|
+
Window.redisplay
|
30
|
+
if yes_or_no?("Dumped buffers found; restore them?")
|
31
|
+
buffers = Buffer.load_dumped_buffers(CONFIG[:buffer_dump_dir])
|
32
|
+
switch_to_buffer(buffers.last)
|
33
|
+
end
|
34
|
+
end
|
29
35
|
rescue Exception => e
|
30
36
|
handle_exception(e)
|
31
37
|
end
|
32
38
|
Window.redisplay
|
33
|
-
|
34
|
-
Window.redraw
|
39
|
+
begin
|
40
|
+
trap(:CONT) { Window.redraw }
|
41
|
+
rescue ArgumentError
|
35
42
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
43
|
+
begin
|
44
|
+
loop do
|
45
|
+
Controller.current.command_loop(TOP_LEVEL_TAG)
|
46
|
+
Window.redisplay
|
47
|
+
end
|
48
|
+
rescue Exception => e
|
49
|
+
if !e.is_a?(SystemExit)
|
50
|
+
Buffer.dump_unsaved_buffers(CONFIG[:buffer_dump_dir])
|
51
|
+
end
|
52
|
+
raise
|
39
53
|
end
|
40
54
|
end
|
data/lib/textbringer/buffer.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require "nkf"
|
4
4
|
require "unicode/display_width"
|
5
|
+
require "json"
|
6
|
+
require "fileutils"
|
5
7
|
|
6
8
|
module Textbringer
|
7
9
|
class Buffer
|
@@ -10,6 +12,7 @@ module Textbringer
|
|
10
12
|
attr_accessor :mode, :keymap
|
11
13
|
attr_reader :name, :file_name, :file_encoding, :file_format, :point, :marks
|
12
14
|
attr_reader :current_line, :current_column, :visible_mark
|
15
|
+
attr_writer :modified
|
13
16
|
|
14
17
|
GAP_SIZE = 256
|
15
18
|
UNDO_LIMIT = 1000
|
@@ -1092,6 +1095,52 @@ module Textbringer
|
|
1092
1095
|
insert(s)
|
1093
1096
|
end
|
1094
1097
|
|
1098
|
+
def dump(path)
|
1099
|
+
File.write(path, to_s)
|
1100
|
+
metadata = {
|
1101
|
+
"name" => name,
|
1102
|
+
"file_name" => file_name,
|
1103
|
+
"file_encoding" => file_encoding.name,
|
1104
|
+
"file_format" => file_format.to_s
|
1105
|
+
}
|
1106
|
+
File.write(path + ".metadata", metadata.to_json)
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def self.load(path)
|
1110
|
+
buffer = Buffer.new(File.read(path))
|
1111
|
+
metadata = JSON.parse(File.read(path + ".metadata"))
|
1112
|
+
buffer.name = metadata["name"]
|
1113
|
+
buffer.file_name = metadata["file_name"] if metadata["file_name"]
|
1114
|
+
buffer.file_encoding = Encoding.find(metadata["file_encoding"])
|
1115
|
+
buffer.file_format = metadata["file_format"].intern
|
1116
|
+
buffer.modified = true
|
1117
|
+
buffer
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def self.dump_unsaved_buffers(dir)
|
1121
|
+
FileUtils.mkdir_p(dir)
|
1122
|
+
@@list.each do |buffer|
|
1123
|
+
if /\A\*/ !~ buffer.name && buffer.modified?
|
1124
|
+
buffer.dump(File.expand_path(buffer.object_id.to_s, dir))
|
1125
|
+
end
|
1126
|
+
end
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
def self.dumped_buffers_exist?(dir)
|
1130
|
+
!Dir.glob(File.expand_path("*.metadata", dir)).empty?
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
def self.load_dumped_buffers(dir)
|
1134
|
+
Dir.glob(File.expand_path("*.metadata", dir)).map do |metadata_path|
|
1135
|
+
path = metadata_path.sub(/\.metadata\z/, "")
|
1136
|
+
buffer = Buffer.load(path)
|
1137
|
+
add(buffer)
|
1138
|
+
File.unlink(metadata_path)
|
1139
|
+
File.unlink(path)
|
1140
|
+
buffer
|
1141
|
+
end
|
1142
|
+
end
|
1143
|
+
|
1095
1144
|
private
|
1096
1145
|
|
1097
1146
|
def adjust_gap(min_size = 0, pos = @point)
|
data/lib/textbringer/commands.rb
CHANGED
@@ -79,7 +79,7 @@ module Textbringer
|
|
79
79
|
end
|
80
80
|
|
81
81
|
define_command(:self_insert) do |n = number_prefix_arg|
|
82
|
-
c = Controller.current.last_key
|
82
|
+
c = Controller.current.last_key
|
83
83
|
merge_undo = Controller.current.last_command == :self_insert
|
84
84
|
n.times do
|
85
85
|
Buffer.current.insert(c, merge_undo)
|
@@ -88,12 +88,11 @@ module Textbringer
|
|
88
88
|
|
89
89
|
define_command(:quoted_insert) do |n = number_prefix_arg|
|
90
90
|
c = Controller.current.read_char
|
91
|
-
if !c.is_a?(
|
91
|
+
if !c.is_a?(String)
|
92
92
|
raise "Invalid key"
|
93
93
|
end
|
94
|
-
ch = c.chr(Encoding::UTF_8)
|
95
94
|
n.times do
|
96
|
-
Buffer.current.insert(
|
95
|
+
Buffer.current.insert(c)
|
97
96
|
end
|
98
97
|
end
|
99
98
|
|
@@ -236,7 +235,7 @@ module Textbringer
|
|
236
235
|
end
|
237
236
|
|
238
237
|
define_command(:suspend_textbringer) do
|
239
|
-
|
238
|
+
Curses.close_screen
|
240
239
|
Process.kill(:STOP, $$)
|
241
240
|
end
|
242
241
|
|
@@ -439,7 +438,7 @@ module Textbringer
|
|
439
438
|
|
440
439
|
define_command(:digit_argument) do
|
441
440
|
|arg = current_prefix_arg|
|
442
|
-
n = last_key.
|
441
|
+
n = last_key.to_i
|
443
442
|
Controller.current.prefix_arg =
|
444
443
|
case arg
|
445
444
|
when Integer
|
@@ -491,18 +490,13 @@ module Textbringer
|
|
491
490
|
end
|
492
491
|
|
493
492
|
ISEARCH_MODE_MAP = Keymap.new
|
494
|
-
(
|
493
|
+
(?\x20..?\x7e).each do |c|
|
495
494
|
ISEARCH_MODE_MAP.define_key(c, :isearch_printing_char)
|
496
495
|
end
|
497
496
|
ISEARCH_MODE_MAP.define_key(?\t, :isearch_printing_char)
|
498
497
|
ISEARCH_MODE_MAP.handle_undefined_key do |key|
|
499
|
-
if key.is_a?(
|
500
|
-
|
501
|
-
key.chr(Encoding::UTF_8)
|
502
|
-
:isearch_printing_char
|
503
|
-
rescue RangeError
|
504
|
-
nil
|
505
|
-
end
|
498
|
+
if key.is_a?(String) && /[\0-\x7f]/ !~ key
|
499
|
+
:isearch_printing_char
|
506
500
|
else
|
507
501
|
nil
|
508
502
|
end
|
@@ -574,7 +568,7 @@ module Textbringer
|
|
574
568
|
end
|
575
569
|
|
576
570
|
define_command(:isearch_printing_char) do
|
577
|
-
c = Controller.current.last_key
|
571
|
+
c = Controller.current.last_key
|
578
572
|
ISEARCH_STATUS[:string].concat(c)
|
579
573
|
isearch_search
|
580
574
|
end
|
data/lib/textbringer/config.rb
CHANGED
@@ -34,7 +34,7 @@ module Textbringer
|
|
34
34
|
catch(tag) do
|
35
35
|
loop do
|
36
36
|
begin
|
37
|
-
c = Window.current.
|
37
|
+
c = Window.current.read_char
|
38
38
|
Window.echo_area.clear_message
|
39
39
|
@last_key = c
|
40
40
|
@key_sequence << @last_key
|
@@ -76,11 +76,11 @@ module Textbringer
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def read_char
|
79
|
-
Window.current.
|
79
|
+
Window.current.read_char
|
80
80
|
end
|
81
81
|
|
82
82
|
def received_keyboard_quit?
|
83
|
-
while
|
83
|
+
while key = Window.current.read_char_nonblock
|
84
84
|
if GLOBAL_MAP.lookup([key]) == :keyboard_quit
|
85
85
|
return true
|
86
86
|
end
|
@@ -105,7 +105,7 @@ module Textbringer
|
|
105
105
|
case key
|
106
106
|
when Integer
|
107
107
|
if key < 0x80
|
108
|
-
s =
|
108
|
+
s = Curses.keyname(key)
|
109
109
|
case s
|
110
110
|
when /\AKEY_(.*)/
|
111
111
|
"<#{$1.downcase}>"
|
data/lib/textbringer/keymap.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "curses"
|
4
4
|
|
5
5
|
module Textbringer
|
6
6
|
class Keymap
|
@@ -43,10 +43,10 @@ module Textbringer
|
|
43
43
|
|
44
44
|
def kbd(key)
|
45
45
|
case key
|
46
|
-
when
|
46
|
+
when Symbol
|
47
47
|
[key]
|
48
48
|
when String
|
49
|
-
key.
|
49
|
+
key.chars
|
50
50
|
when Array
|
51
51
|
key
|
52
52
|
else
|
@@ -80,7 +80,7 @@ module Textbringer
|
|
80
80
|
GLOBAL_MAP.define_key(:end, :end_of_line)
|
81
81
|
GLOBAL_MAP.define_key("\e<", :beginning_of_buffer)
|
82
82
|
GLOBAL_MAP.define_key("\e>", :end_of_buffer)
|
83
|
-
(
|
83
|
+
(?\x20..?\x7e).each do |c|
|
84
84
|
GLOBAL_MAP.define_key(c, :self_insert)
|
85
85
|
end
|
86
86
|
GLOBAL_MAP.define_key(?\t, :self_insert)
|
@@ -123,13 +123,8 @@ module Textbringer
|
|
123
123
|
GLOBAL_MAP.define_key(?\C-r, :isearch_backward)
|
124
124
|
GLOBAL_MAP.define_key("\e!", :shell_execute)
|
125
125
|
GLOBAL_MAP.handle_undefined_key do |key|
|
126
|
-
if key.is_a?(
|
127
|
-
|
128
|
-
key.chr(Encoding::UTF_8)
|
129
|
-
:self_insert
|
130
|
-
rescue RangeError
|
131
|
-
nil
|
132
|
-
end
|
126
|
+
if key.is_a?(String) && /[\0-\x7f]/ !~ key
|
127
|
+
:self_insert
|
133
128
|
else
|
134
129
|
nil
|
135
130
|
end
|
data/lib/textbringer/version.rb
CHANGED
data/lib/textbringer/window.rb
CHANGED
@@ -1,20 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "curses"
|
4
4
|
require "unicode/display_width"
|
5
|
+
require "fiddle/import"
|
5
6
|
|
6
7
|
module Textbringer
|
8
|
+
begin
|
9
|
+
# These features should be provided by curses.gem.
|
10
|
+
module PDCurses
|
11
|
+
extend Fiddle::Importer
|
12
|
+
dlload "pdcurses.dll"
|
13
|
+
extern "unsigned long PDC_get_key_modifiers(void)"
|
14
|
+
extern "int PDC_save_key_modifiers(unsigned char)"
|
15
|
+
extern "int PDC_return_key_modifiers(unsigned char)"
|
16
|
+
|
17
|
+
KEY_MODIFIER_SHIFT = 1
|
18
|
+
KEY_MODIFIER_CONTROL = 2
|
19
|
+
KEY_MODIFIER_ALT = 4
|
20
|
+
KEY_MODIFIER_NUMLOCK = 8
|
21
|
+
end
|
22
|
+
rescue
|
23
|
+
remove_const :PDCurses
|
24
|
+
end
|
25
|
+
|
7
26
|
class Window
|
8
27
|
KEY_NAMES = {}
|
9
|
-
|
10
|
-
KEY_NAMES[
|
28
|
+
Curses.constants.grep(/\AKEY_/).each do |name|
|
29
|
+
KEY_NAMES[Curses.const_get(name)] =
|
11
30
|
name.slice(/\AKEY_(.*)/, 1).downcase.intern
|
12
31
|
end
|
13
32
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
33
|
+
ALT_IS_FUNCTION_KEY =
|
34
|
+
/mswin32|mingw32/ =~ RUBY_PLATFORM && /PDCurses/ =~ Curses::VERSION
|
35
|
+
if ALT_IS_FUNCTION_KEY
|
36
|
+
KEY_OFFSET = 0xec00
|
37
|
+
ALT_0 = KEY_OFFSET + 0x97
|
38
|
+
ALT_9 = KEY_OFFSET + 0xa0
|
39
|
+
ALT_A = KEY_OFFSET + 0xa1
|
40
|
+
ALT_Z = KEY_OFFSET + 0xba
|
41
|
+
ALT_NUMBER_BASE = ALT_0 - ?0.ord
|
42
|
+
ALT_ALPHA_BASE = ALT_A - ?a.ord
|
43
|
+
end
|
18
44
|
|
19
45
|
@@windows = []
|
20
46
|
@@current = nil
|
@@ -88,9 +114,9 @@ module Textbringer
|
|
88
114
|
end
|
89
115
|
|
90
116
|
def self.start
|
91
|
-
|
92
|
-
|
93
|
-
|
117
|
+
Curses.init_screen
|
118
|
+
Curses.noecho
|
119
|
+
Curses.raw
|
94
120
|
begin
|
95
121
|
window =
|
96
122
|
Textbringer::Window.new(Window.lines - 1, Window.columns, 0, 0)
|
@@ -99,14 +125,14 @@ module Textbringer
|
|
99
125
|
Window.current = window
|
100
126
|
@@echo_area = Textbringer::EchoArea.new(1, Window.columns,
|
101
127
|
Window.lines - 1, 0)
|
102
|
-
|
103
|
-
|
104
|
-
|
128
|
+
Buffer.minibuffer.keymap = MINIBUFFER_LOCAL_MAP
|
129
|
+
@@echo_area.buffer = Buffer.minibuffer
|
130
|
+
@@windows.push(@@echo_area)
|
105
131
|
yield
|
106
132
|
ensure
|
107
|
-
|
108
|
-
|
109
|
-
|
133
|
+
Curses.echo
|
134
|
+
Curses.noraw
|
135
|
+
Curses.close_screen
|
110
136
|
end
|
111
137
|
end
|
112
138
|
|
@@ -127,20 +153,20 @@ module Textbringer
|
|
127
153
|
end
|
128
154
|
|
129
155
|
def self.update
|
130
|
-
|
156
|
+
Curses.doupdate
|
131
157
|
end
|
132
158
|
|
133
159
|
def self.lines
|
134
|
-
|
160
|
+
Curses.lines
|
135
161
|
end
|
136
162
|
|
137
163
|
def self.columns
|
138
|
-
|
164
|
+
Curses.cols
|
139
165
|
end
|
140
166
|
|
141
167
|
def self.resize
|
142
168
|
@@windows.delete_if do |window|
|
143
|
-
if window.y > Window.lines - 4
|
169
|
+
if !window.echo_area? && window.y > Window.lines - 4
|
144
170
|
window.delete
|
145
171
|
true
|
146
172
|
else
|
@@ -148,10 +174,12 @@ module Textbringer
|
|
148
174
|
end
|
149
175
|
end
|
150
176
|
@@windows.each_with_index do |window, i|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
177
|
+
unless window.echo_area?
|
178
|
+
if i < @@windows.size - 2
|
179
|
+
window.resize(window.lines, Window.columns)
|
180
|
+
else
|
181
|
+
window.resize(Window.lines - 1 - window.y, Window.columns)
|
182
|
+
end
|
155
183
|
end
|
156
184
|
end
|
157
185
|
@@echo_area.move(Window.lines - 1, 0)
|
@@ -159,7 +187,7 @@ module Textbringer
|
|
159
187
|
end
|
160
188
|
|
161
189
|
def self.beep
|
162
|
-
|
190
|
+
Curses.beep
|
163
191
|
end
|
164
192
|
|
165
193
|
attr_reader :buffer, :lines, :columns, :y, :x
|
@@ -170,7 +198,7 @@ module Textbringer
|
|
170
198
|
@y = y
|
171
199
|
@x = x
|
172
200
|
initialize_window(lines, columns, y, x)
|
173
|
-
@window.keypad
|
201
|
+
@window.keypad = true
|
174
202
|
@window.scrollok(false)
|
175
203
|
@window.idlok(true)
|
176
204
|
@buffer = nil
|
@@ -178,6 +206,7 @@ module Textbringer
|
|
178
206
|
@bottom_of_window = nil
|
179
207
|
@point_mark = nil
|
180
208
|
@deleted = false
|
209
|
+
@key_buffer = []
|
181
210
|
end
|
182
211
|
|
183
212
|
def echo_area?
|
@@ -198,7 +227,7 @@ module Textbringer
|
|
198
227
|
Window.current = @@windows.first
|
199
228
|
end
|
200
229
|
delete_marks
|
201
|
-
@window.
|
230
|
+
@window.close
|
202
231
|
@deleted = true
|
203
232
|
end
|
204
233
|
end
|
@@ -233,58 +262,61 @@ module Textbringer
|
|
233
262
|
self == @@current
|
234
263
|
end
|
235
264
|
|
236
|
-
def
|
237
|
-
key =
|
238
|
-
if key.
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
else
|
247
|
-
buf = [key]
|
248
|
-
(len - 1).times do
|
249
|
-
c = @window.getch
|
250
|
-
if c.nil? || c < 0x80 || c > 0xbf
|
251
|
-
raise EditorError, "Malformed UTF-8 input"
|
252
|
-
end
|
253
|
-
buf.push(c)
|
254
|
-
end
|
255
|
-
s = buf.pack("C*").force_encoding(Encoding::UTF_8)
|
256
|
-
if s.valid_encoding?
|
257
|
-
s.ord
|
258
|
-
else
|
259
|
-
raise EditorError, "Malformed UTF-8 input"
|
265
|
+
def read_char
|
266
|
+
key = get_char
|
267
|
+
if key.is_a?(Integer)
|
268
|
+
if ALT_IS_FUNCTION_KEY
|
269
|
+
if ALT_0 <= key && key <= ALT_9
|
270
|
+
@key_buffer.push((key - ALT_NUMBER_BASE).chr)
|
271
|
+
return "\e"
|
272
|
+
elsif ALT_A <= key && key <= ALT_Z
|
273
|
+
@key_buffer.push((key - ALT_ALPHA_BASE).chr)
|
274
|
+
return "\e"
|
260
275
|
end
|
261
276
|
end
|
277
|
+
KEY_NAMES[key] || key
|
278
|
+
else
|
279
|
+
key&.encode(Encoding::UTF_8)&.tr("\r", "\n")
|
262
280
|
end
|
263
281
|
end
|
264
282
|
|
265
|
-
def
|
266
|
-
@window.nodelay
|
283
|
+
def read_char_nonblock
|
284
|
+
@window.nodelay = true
|
267
285
|
begin
|
268
|
-
|
286
|
+
read_char
|
269
287
|
ensure
|
270
|
-
@window.nodelay
|
288
|
+
@window.nodelay = false
|
271
289
|
end
|
272
290
|
end
|
273
291
|
|
274
292
|
def wait_input(msecs)
|
275
|
-
@window.timeout
|
293
|
+
@window.timeout = msecs
|
276
294
|
begin
|
277
|
-
c = @window.
|
278
|
-
if c
|
279
|
-
|
295
|
+
c = @window.get_char
|
296
|
+
if c
|
297
|
+
Curses.unget_char(c)
|
280
298
|
end
|
281
299
|
c
|
282
300
|
ensure
|
283
|
-
@window.timeout
|
301
|
+
@window.timeout = -1
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def has_input?
|
306
|
+
@window.nodelay = true
|
307
|
+
begin
|
308
|
+
c = @window.get_char
|
309
|
+
if c
|
310
|
+
Curses.unget_char(c)
|
311
|
+
end
|
312
|
+
!c.nil?
|
313
|
+
ensure
|
314
|
+
@window.nodelay = false
|
284
315
|
end
|
285
316
|
end
|
286
317
|
|
287
318
|
def redisplay
|
319
|
+
return if has_input?
|
288
320
|
return if @buffer.nil?
|
289
321
|
redisplay_mode_line
|
290
322
|
@buffer.save_point do |saved|
|
@@ -298,51 +330,51 @@ module Textbringer
|
|
298
330
|
y = x = 0
|
299
331
|
@buffer.point_to_mark(@top_of_window)
|
300
332
|
@window.erase
|
301
|
-
@window.
|
333
|
+
@window.setpos(0, 0)
|
302
334
|
if current? && @buffer.visible_mark &&
|
303
335
|
@buffer.point_after_mark?(@buffer.visible_mark)
|
304
|
-
@window.attron(
|
336
|
+
@window.attron(Curses::A_REVERSE)
|
305
337
|
end
|
306
338
|
while !@buffer.end_of_buffer?
|
307
339
|
if @buffer.point_at_mark?(point)
|
308
|
-
y, x = @window.
|
340
|
+
y, x = @window.cury, @window.curx
|
309
341
|
if current? && @buffer.visible_mark
|
310
342
|
if @buffer.point_after_mark?(@buffer.visible_mark)
|
311
|
-
@window.attroff(
|
343
|
+
@window.attroff(Curses::A_REVERSE)
|
312
344
|
elsif @buffer.point_before_mark?(@buffer.visible_mark)
|
313
|
-
@window.attron(
|
345
|
+
@window.attron(Curses::A_REVERSE)
|
314
346
|
end
|
315
347
|
end
|
316
348
|
end
|
317
349
|
if current? && @buffer.visible_mark &&
|
318
350
|
@buffer.point_at_mark?(@buffer.visible_mark)
|
319
351
|
if @buffer.point_after_mark?(point)
|
320
|
-
@window.attroff(
|
352
|
+
@window.attroff(Curses::A_REVERSE)
|
321
353
|
elsif @buffer.point_before_mark?(point)
|
322
|
-
@window.attron(
|
354
|
+
@window.attron(Curses::A_REVERSE)
|
323
355
|
end
|
324
356
|
end
|
325
357
|
c = @buffer.char_after
|
326
358
|
if c == "\n"
|
327
359
|
@window.clrtoeol
|
328
|
-
break if @window.
|
360
|
+
break if @window.cury == lines - 2 # lines include mode line
|
329
361
|
elsif c == "\t"
|
330
|
-
n = calc_tab_width(@window.
|
362
|
+
n = calc_tab_width(@window.curx)
|
331
363
|
c = " " * n
|
332
364
|
else
|
333
365
|
c = escape(c)
|
334
366
|
end
|
335
367
|
@window.addstr(c)
|
336
|
-
break if @window.
|
337
|
-
@window.
|
368
|
+
break if @window.cury == lines - 2 && # lines include mode line
|
369
|
+
@window.curx == columns
|
338
370
|
@buffer.forward_char
|
339
371
|
end
|
340
372
|
if current? && @buffer.visible_mark
|
341
|
-
@window.attroff(
|
373
|
+
@window.attroff(Curses::A_REVERSE)
|
342
374
|
end
|
343
375
|
@buffer.mark_to_point(@bottom_of_window)
|
344
376
|
if @buffer.point_at_mark?(point)
|
345
|
-
y, x = @window.
|
377
|
+
y, x = @window.cury, @window.curx
|
346
378
|
end
|
347
379
|
if x == columns - 1
|
348
380
|
c = @buffer.char_after(point.location)
|
@@ -351,28 +383,28 @@ module Textbringer
|
|
351
383
|
x = 0
|
352
384
|
end
|
353
385
|
end
|
354
|
-
@window.
|
386
|
+
@window.setpos(y, x)
|
355
387
|
@window.noutrefresh
|
356
388
|
end
|
357
389
|
end
|
358
390
|
|
359
391
|
def redraw
|
360
|
-
@window.
|
361
|
-
@mode_line.
|
392
|
+
@window.redraw
|
393
|
+
@mode_line.redraw
|
362
394
|
end
|
363
395
|
|
364
396
|
def move(y, x)
|
365
397
|
@y = y
|
366
398
|
@x = x
|
367
|
-
@window.
|
368
|
-
@mode_line.
|
399
|
+
@window.move(y, x)
|
400
|
+
@mode_line.move(y + @window.maxy, x)
|
369
401
|
end
|
370
402
|
|
371
403
|
def resize(lines, columns)
|
372
404
|
@lines = lines
|
373
405
|
@columns = columns
|
374
406
|
@window.resize(lines - 1, columns)
|
375
|
-
@mode_line.
|
407
|
+
@mode_line.move(@y + lines - 1, @x)
|
376
408
|
@mode_line.resize(1, columns)
|
377
409
|
end
|
378
410
|
|
@@ -426,8 +458,8 @@ module Textbringer
|
|
426
458
|
private
|
427
459
|
|
428
460
|
def initialize_window(num_lines, num_columns, y, x)
|
429
|
-
@window =
|
430
|
-
@mode_line =
|
461
|
+
@window = Curses::Window.new(num_lines - 1, num_columns, y, x)
|
462
|
+
@mode_line = Curses::Window.new(1, num_columns, y + num_lines - 1, x)
|
431
463
|
end
|
432
464
|
|
433
465
|
def framer
|
@@ -454,8 +486,8 @@ module Textbringer
|
|
454
486
|
|
455
487
|
def redisplay_mode_line
|
456
488
|
@mode_line.erase
|
457
|
-
@mode_line.
|
458
|
-
@mode_line.attron(
|
489
|
+
@mode_line.setpos(0, 0)
|
490
|
+
@mode_line.attron(Curses::A_REVERSE)
|
459
491
|
@mode_line.addstr("#{@buffer.name} ")
|
460
492
|
@mode_line.addstr("[+]") if @buffer.modified?
|
461
493
|
@mode_line.addstr("[RO]") if @buffer.read_only?
|
@@ -472,8 +504,8 @@ module Textbringer
|
|
472
504
|
@mode_line.addstr(unicode_codepoint(c))
|
473
505
|
@mode_line.addstr(" #{line},#{column}")
|
474
506
|
@mode_line.addstr(" (#{@buffer.mode&.name || 'None'})")
|
475
|
-
@mode_line.addstr(" " * (@mode_line.
|
476
|
-
@mode_line.attroff(
|
507
|
+
@mode_line.addstr(" " * (@mode_line.maxx - @mode_line.curx))
|
508
|
+
@mode_line.attroff(Curses::A_REVERSE)
|
477
509
|
@mode_line.noutrefresh
|
478
510
|
end
|
479
511
|
|
@@ -508,6 +540,9 @@ module Textbringer
|
|
508
540
|
def beginning_of_line_and_count(max_lines)
|
509
541
|
e = @buffer.point
|
510
542
|
@buffer.beginning_of_line
|
543
|
+
if e - @buffer.point < @columns
|
544
|
+
return 0
|
545
|
+
end
|
511
546
|
s = @buffer.substring(@buffer.point, e)
|
512
547
|
bols = [@buffer.point]
|
513
548
|
column = 0
|
@@ -556,6 +591,36 @@ module Textbringer
|
|
556
591
|
@point_mark = nil
|
557
592
|
end
|
558
593
|
end
|
594
|
+
|
595
|
+
def get_char
|
596
|
+
if @key_buffer.empty?
|
597
|
+
PDCurses.PDC_save_key_modifiers(1) if defined?(PDCurses)
|
598
|
+
need_retry = false
|
599
|
+
begin
|
600
|
+
key = @window.get_char
|
601
|
+
if defined?(PDCurses)
|
602
|
+
mods = PDCurses.PDC_get_key_modifiers
|
603
|
+
if key.is_a?(String) && key.ascii_only?
|
604
|
+
if (mods & PDCurses::KEY_MODIFIER_CONTROL) != 0
|
605
|
+
key = key == ?? ? "\x7f" : (key.ord & 0x9f).chr
|
606
|
+
end
|
607
|
+
if (mods & PDCurses::KEY_MODIFIER_ALT) != 0
|
608
|
+
if key == "\0"
|
609
|
+
# Alt + `, Alt + < etc. return NUL, so ignore it.
|
610
|
+
need_retry = true
|
611
|
+
else
|
612
|
+
@key_buffer.push(key)
|
613
|
+
key = "\e"
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end while need_retry
|
619
|
+
key
|
620
|
+
else
|
621
|
+
@key_buffer.shift
|
622
|
+
end
|
623
|
+
end
|
559
624
|
end
|
560
625
|
|
561
626
|
class EchoArea < Window
|
@@ -595,40 +660,40 @@ module Textbringer
|
|
595
660
|
return if @buffer.nil?
|
596
661
|
@buffer.save_point do |saved|
|
597
662
|
@window.erase
|
598
|
-
@window.
|
663
|
+
@window.setpos(0, 0)
|
599
664
|
if @message
|
600
|
-
@window.addstr
|
665
|
+
@window.addstr(escape(@message))
|
601
666
|
else
|
602
|
-
@window.addstr
|
667
|
+
@window.addstr(escape(@prompt))
|
603
668
|
@buffer.beginning_of_line
|
604
669
|
while !@buffer.end_of_buffer?
|
605
670
|
if @buffer.point_at_mark?(saved)
|
606
|
-
y, x = @window.
|
671
|
+
y, x = @window.cury, @window.curx
|
607
672
|
end
|
608
673
|
c = @buffer.char_after
|
609
674
|
if c == "\n"
|
610
675
|
break
|
611
676
|
end
|
612
|
-
@window.addstr
|
677
|
+
@window.addstr(escape(c))
|
613
678
|
@buffer.forward_char
|
614
679
|
end
|
615
680
|
if @buffer.point_at_mark?(saved)
|
616
|
-
y, x = @window.
|
681
|
+
y, x = @window.cury, @window.curx
|
617
682
|
end
|
618
|
-
@window.
|
683
|
+
@window.setpos(y, x)
|
619
684
|
end
|
620
685
|
@window.noutrefresh
|
621
686
|
end
|
622
687
|
end
|
623
688
|
|
624
689
|
def redraw
|
625
|
-
@window.
|
690
|
+
@window.redraw
|
626
691
|
end
|
627
692
|
|
628
693
|
def move(y, x)
|
629
694
|
@y = y
|
630
695
|
@x = x
|
631
|
-
@window.
|
696
|
+
@window.move(y, x)
|
632
697
|
end
|
633
698
|
|
634
699
|
def resize(lines, columns)
|
@@ -640,7 +705,7 @@ module Textbringer
|
|
640
705
|
private
|
641
706
|
|
642
707
|
def initialize_window(num_lines, num_columns, y, x)
|
643
|
-
@window =
|
708
|
+
@window = Curses::Window.new(num_lines, num_columns, y, x)
|
644
709
|
end
|
645
710
|
end
|
646
711
|
end
|
data/textbringer.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.required_ruby_version = '>= 2.3'
|
23
23
|
|
24
|
-
spec.add_runtime_dependency "
|
24
|
+
spec.add_runtime_dependency "curses", "~> 1.1"
|
25
25
|
spec.add_runtime_dependency "unicode-display_width", "~> 1.1"
|
26
26
|
|
27
27
|
spec.add_development_dependency "bundler", "~> 1.11"
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: textbringer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: curses
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: unicode-display_width
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,8 +119,8 @@ extra_rdoc_files: []
|
|
119
119
|
files:
|
120
120
|
- ".gitignore"
|
121
121
|
- ".travis.yml"
|
122
|
+
- CHANGES.md
|
122
123
|
- Gemfile
|
123
|
-
- Gemfile.lock
|
124
124
|
- LICENSE.txt
|
125
125
|
- README.md
|
126
126
|
- Rakefile
|