textbringer 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acc4214c3d7c5b5535435505b2504db1f80d46ba
4
- data.tar.gz: e0ad741989368ad91ce5d2b5100896297d28a059
3
+ metadata.gz: b6dafe6a94443fe1e79c7a9316973fc43ec01525
4
+ data.tar.gz: 8332b5d2ef6dd7cbb4df030b9e2ce79a4551be83
5
5
  SHA512:
6
- metadata.gz: f4adfeefebf234a5329c2bfda93f8d9ffe4a83fc029a57579fa8e72d58802aa92be479949a1243f540047ad495efa6507dbbd1d1f61cd5c27e8e3f17225160f3
7
- data.tar.gz: 8edab0edf73fbff79c8a16b62a9863a6aa5ee5867abbc494e9bb261c2885fc5e277ac9957731e80adf8806fbcfa15a8e11096094259f0e4ebf7390ba16e3a21e
6
+ metadata.gz: 5d7687449b608e273a1a0a11155e80e07543095e0bf73deab13740767166274590803d28e203a5816a6526d44a25bf49ac36855deecc1833a007b4a84525b933
7
+ data.tar.gz: 4cf790621ce7c55960cc7e0087fb6dac140280d60adf20e2e0b9b88f69eef252226cf9bdbf975614bdf56658cbc90de074f568c4a9775fb0c983206fa5951293
@@ -1,12 +1,25 @@
1
1
  sudo: false
2
+ os:
3
+ - linux
4
+ - osx
2
5
  language: ruby
3
6
  rvm:
4
- - 2.3.3
5
- - 2.4.0
7
+ - 2.3.3
8
+ - 2.4.0
9
+ - ruby-head
10
+ matrix:
11
+ allow_failures:
12
+ - os: osx
13
+ - rvm: ruby-head
14
+ fast_finish: true
6
15
  cache: bundler
7
16
  script:
8
- - xvfb-run bundle exec rake test
9
- - bundle exec bundle-audit check --update
17
+ - if [ "$TRAVIS_OS_NAME" == "linux" ]; then
18
+ xvfb-run bundle exec rake test;
19
+ else
20
+ bundle exec rake test;
21
+ fi
22
+ - bundle exec bundle-audit check --update
10
23
  env:
11
24
  global:
12
25
  UPLOAD_TO_CODECOV: 1
@@ -14,3 +27,7 @@ notifications:
14
27
  email:
15
28
  on_success: never
16
29
  on_failure: always
30
+ addons:
31
+ apt:
32
+ packages:
33
+ - xclip
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.1.8
2
+
3
+ * Support syntax highlighting.
4
+ * Add the commands grep, gsub, and enlarge_window.
5
+ * Don't translate CR into LF.
6
+
1
7
  ## 0.1.7
2
8
 
3
9
  * Support EditorConfig.
data/README.md CHANGED
@@ -11,7 +11,9 @@ text editor.
11
11
 
12
12
  ## Demo
13
13
 
14
- [![asciicast](https://asciinema.org/a/100156.png)](https://asciinema.org/a/100156)
14
+ * FizzBuzz: https://asciinema.org/a/103357
15
+ * Ruby Programming: https://asciinema.org/a/100156
16
+ * Japanese Text Editing: https://asciinema.org/a/100166
15
17
 
16
18
  ## WARNING
17
19
 
@@ -25,6 +27,12 @@ minor versions.
25
27
 
26
28
  $ gem install textbringer
27
29
 
30
+ You need ncursesw to use multibyte characters.
31
+ Install ncursesw before installing curses.gem, on which textbringer depends.
32
+
33
+ $ sudo apt-get install libncursesw5-dev
34
+ $ gem install curses
35
+
28
36
  ## Usage
29
37
 
30
38
  $ textbringer
@@ -33,7 +33,7 @@ Window.start do
33
33
  end
34
34
  end
35
35
  rescue Exception => e
36
- handle_exception(e)
36
+ show_exception(e)
37
37
  end
38
38
  Window.redisplay
39
39
  begin
@@ -5,6 +5,8 @@ require_relative "textbringer/buffer"
5
5
  require_relative "textbringer/window"
6
6
  require_relative "textbringer/keymap"
7
7
  require_relative "textbringer/utils"
8
+ require_relative "textbringer/color"
9
+ require_relative "textbringer/face"
8
10
  require_relative "textbringer/commands"
9
11
  require_relative "textbringer/commands/buffers"
10
12
  require_relative "textbringer/commands/windows"
@@ -162,7 +162,7 @@ module Textbringer
162
162
  end
163
163
 
164
164
  def expand_tab(s)
165
- # TOOD: Support multibyte characters
165
+ # TODO: Support multibyte characters
166
166
  tw = self[:tab_width]
167
167
  fmt = "A#{tw}"
168
168
  s.b.gsub(/([^\t]{#{tw}})|([^\t]*)\t/n) {
@@ -481,7 +481,8 @@ module Textbringer
481
481
  @goal_column = nil
482
482
  end
483
483
 
484
- def insert(s, merge_undo = false)
484
+ def insert(x, merge_undo = false)
485
+ s = x.to_s
485
486
  check_read_only_flag
486
487
  pos = @point
487
488
  size = s.bytesize
@@ -504,6 +505,7 @@ module Textbringer
504
505
  end
505
506
  @modified = true
506
507
  @goal_column = nil
508
+ self
507
509
  end
508
510
 
509
511
  def newline
@@ -1185,6 +1187,14 @@ module Textbringer
1185
1187
  end
1186
1188
  end
1187
1189
 
1190
+ def gsub(*args, &block)
1191
+ s = to_s.gsub(*args, &block)
1192
+ delete_region(point_min, point_max)
1193
+ insert(s)
1194
+ merge_undo(2)
1195
+ self
1196
+ end
1197
+
1188
1198
  private
1189
1199
 
1190
1200
  def adjust_gap(min_size = 0, pos = @point)
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "curses"
4
+
5
+ module Textbringer
6
+ module Color
7
+ BASIC_COLORS = {
8
+ "default" => -1,
9
+ "black" => Curses::COLOR_BLACK,
10
+ "red" => Curses::COLOR_RED,
11
+ "green" => Curses::COLOR_GREEN,
12
+ "yellow" => Curses::COLOR_YELLOW,
13
+ "blue" => Curses::COLOR_BLUE,
14
+ "magenta" => Curses::COLOR_MAGENTA,
15
+ "cyan" => Curses::COLOR_CYAN,
16
+ "white" => Curses::COLOR_WHITE,
17
+ "brightblack" => 8,
18
+ "brightred" => 9,
19
+ "brightgreen" => 10,
20
+ "brightyellow" => 11,
21
+ "brightblue" => 12,
22
+ "brightmagenta" => 13,
23
+ "brightcyan" => 14,
24
+ "brightwhite" => 15
25
+ }
26
+
27
+ RGBColor = Struct.new(:r, :g, :b, :number)
28
+
29
+ ADDITIONAL_COLORS = []
30
+ rgb_values = [0, 0x5F, 0x87, 0xAF, 0xD7, 0xFF]
31
+ rgb_values.product(rgb_values, rgb_values).each_with_index do
32
+ |(r, g, b), i|
33
+ ADDITIONAL_COLORS << RGBColor.new(r, g, b, 16 + i)
34
+ end
35
+ [
36
+ 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
37
+ 0x80, 0x8a, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
38
+ ].each_with_index do |v, i|
39
+ ADDITIONAL_COLORS << RGBColor.new(v, v, v, 232 + i)
40
+ end
41
+
42
+ def self.[](name)
43
+ n = find_color_number(name)
44
+ if n < Window.colors
45
+ n
46
+ else
47
+ -1
48
+ end
49
+ end
50
+
51
+ def self.find_color_number(name)
52
+ if name.is_a?(Integer)
53
+ return name
54
+ end
55
+ case name
56
+ when /\A\#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})\z/
57
+ r = $1.to_i(16)
58
+ g = $2.to_i(16)
59
+ b = $3.to_i(16)
60
+ ADDITIONAL_COLORS.sort_by { |c|
61
+ (r - c.r) ** 2 + (g - c.g) ** 2 + (b - c.b) ** 2
62
+ }.first.number
63
+ else
64
+ unless BASIC_COLORS.key?(name)
65
+ raise EditorError, "No such color: #{name}"
66
+ end
67
+ BASIC_COLORS[name]
68
+ end
69
+ end
70
+ end
71
+ end
@@ -16,6 +16,7 @@ module Textbringer
16
16
  def define_command(name, &block)
17
17
  Commands.send(:define_method, name, &block)
18
18
  @@command_list << name if !@@command_list.include?(name)
19
+ name
19
20
  end
20
21
  module_function :define_command
21
22
 
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module Clipboard
4
+ @implementation = nil
5
+ end
6
+
3
7
  require "clipboard"
4
8
 
5
9
  module Textbringer
@@ -14,7 +14,6 @@ module Textbringer
14
14
  index: nil,
15
15
  tag_mark_stack: []
16
16
  }
17
- TAG_MARK_LIMIT = 16
18
17
 
19
18
  define_command(:find_tag) do |next_p = current_prefix_arg|
20
19
  tags = get_tags
@@ -111,7 +110,7 @@ module Textbringer
111
110
 
112
111
  def push_tag_mark_and_find_file(file)
113
112
  tag_mark_stack = CTAGS[:tag_mark_stack]
114
- if tag_mark_stack.size == TAG_MARK_LIMIT
113
+ if tag_mark_stack.size == CONFIG[:tag_mark_limit]
115
114
  tag_mark_stack.shift.delete
116
115
  end
117
116
  tag_mark_stack.push(Buffer.current.new_mark)
@@ -227,16 +227,21 @@ module Textbringer
227
227
 
228
228
  define_command(:shell_execute) do
229
229
  |cmd = read_from_minibuffer("Shell execute: "),
230
- buffer_name = "*Shell output*"|
230
+ buffer_name: "*Shell output*",
231
+ mode: FundamentalMode|
231
232
  buffer = Buffer.find_or_new(buffer_name)
232
233
  switch_to_buffer(buffer)
234
+ buffer.apply_mode(mode)
233
235
  buffer.read_only = false
234
236
  buffer.clear
235
237
  Window.redisplay
236
238
  signals = [:INT, :TERM, :KILL]
237
239
  begin
238
240
  opts = /mswin32|mingw32/ =~ RUBY_PLATFORM ? {} : {pgroup: true}
239
- Open3.popen2e(cmd, opts) do |input, output, wait_thread|
241
+ if CONFIG[:shell_file_name]
242
+ cmd = [CONFIG[:shell_file_name], CONFIG[:shell_command_switch], cmd]
243
+ end
244
+ Open3.popen2e(*cmd, opts) do |input, output, wait_thread|
240
245
  input.close
241
246
  loop do
242
247
  status = output.wait_readable(0.5)
@@ -278,5 +283,11 @@ module Textbringer
278
283
  buffer.read_only = true
279
284
  end
280
285
  end
286
+
287
+ define_command(:grep) do
288
+ |cmd = read_from_minibuffer("Grep: ",
289
+ initial_value: CONFIG[:grep_command] + " ")|
290
+ shell_execute(cmd, buffer_name: "*grep*", mode: BacktraceMode)
291
+ end
281
292
  end
282
293
  end
@@ -35,6 +35,10 @@ module Textbringer
35
35
  Window.other_window
36
36
  end
37
37
 
38
+ define_command(:enlarge_window) do |n = number_prefix_arg|
39
+ Window.current.enlarge(n)
40
+ end
41
+
38
42
  define_command(:switch_to_buffer) do
39
43
  |buffer_name = read_buffer("Switch to buffer: ")|
40
44
  if buffer_name.is_a?(Buffer)
@@ -8,6 +8,13 @@ module Textbringer
8
8
  tab_width: 8,
9
9
  indent_tabs_mode: false,
10
10
  case_fold_search: true,
11
- buffer_dump_dir: File.expand_path("~/.textbringer/buffer_dump")
11
+ buffer_dump_dir: File.expand_path("~/.textbringer/buffer_dump"),
12
+ tag_mark_limit: 16,
13
+ window_min_height: 4,
14
+ syntax_highlight: true,
15
+ highlight_buffer_size_limit: 102400,
16
+ shell_file_name: ENV["SHELL"],
17
+ shell_command_switch: "-c",
18
+ grep_command: "grep -nH -e"
12
19
  }
13
20
  end
@@ -7,7 +7,7 @@ module Textbringer
7
7
  class Controller
8
8
  attr_accessor :this_command, :last_command, :overriding_map
9
9
  attr_accessor :prefix_arg, :current_prefix_arg
10
- attr_reader :last_key, :recursive_edit_level
10
+ attr_reader :key_sequence, :last_key, :recursive_edit_level
11
11
 
12
12
  @@current = nil
13
13
 
@@ -114,7 +114,7 @@ module Textbringer
114
114
  "<#{key}>"
115
115
  when "\e"
116
116
  "ESC"
117
- when "\n"
117
+ when "\C-m"
118
118
  "RET"
119
119
  when /\A[\0-\b\v-\x1f\x7f]\z/
120
120
  "C-" + (key.ord ^ 0x40).chr.downcase
@@ -123,14 +123,6 @@ module Textbringer
123
123
  end
124
124
  end
125
125
 
126
- private
127
-
128
- def key_binding(key_sequence)
129
- @overriding_map&.lookup(key_sequence) ||
130
- Buffer.current&.keymap&.lookup(key_sequence) ||
131
- GLOBAL_MAP.lookup(key_sequence)
132
- end
133
-
134
126
  def echo_input
135
127
  if @prefix_arg || !@key_sequence.empty?
136
128
  if !@echo_immediately
@@ -157,5 +149,13 @@ module Textbringer
157
149
  @echo_immediately = false
158
150
  end
159
151
  end
152
+
153
+ private
154
+
155
+ def key_binding(key_sequence)
156
+ @overriding_map&.lookup(key_sequence) ||
157
+ Buffer.current&.keymap&.lookup(key_sequence) ||
158
+ GLOBAL_MAP.lookup(key_sequence)
159
+ end
160
160
  end
161
161
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "curses"
4
+
5
+ module Textbringer
6
+ class Face
7
+ attr_reader :name, :attributes
8
+
9
+ @@face_table = {}
10
+ @@next_color_pair = 1
11
+
12
+ def self.[](name)
13
+ @@face_table[name]
14
+ end
15
+
16
+ def self.define(name, **opts)
17
+ if @@face_table.key?(name)
18
+ @@face_table[name].update(**opts)
19
+ else
20
+ @@face_table[name] = new(name, **opts)
21
+ end
22
+ end
23
+
24
+ def self.delete(name)
25
+ @@face_table.delete(name)
26
+ end
27
+
28
+ def initialize(name, **opts)
29
+ @name = name
30
+ @color_pair = @@next_color_pair
31
+ @@next_color_pair += 1
32
+ update(**opts)
33
+ end
34
+
35
+ def update(foreground: -1, background: -1,
36
+ bold: false, underline: false, reverse: false)
37
+ @foreground = foreground
38
+ @background = background
39
+ @bold = bold
40
+ @underline = underline
41
+ @reverse = reverse
42
+ Curses.init_pair(@color_pair,
43
+ Color[foreground], Color[background])
44
+ @attributes = 0
45
+ @attributes |= Curses.color_pair(@color_pair)
46
+ @attributes |= Curses::A_BOLD if bold
47
+ @attributes |= Curses::A_UNDERLINE if underline
48
+ @attributes |= Curses::A_REVERSE if reverse
49
+ self
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Textbringer
4
+ Face.define :mode_line, reverse: true
5
+ Face.define :link, foreground: "blue", bold: true
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Textbringer
4
+ Face.define :comment, foreground: "yellow"
5
+ Face.define :preprocessing_directive, foreground: "green"
6
+ Face.define :keyword, foreground: "magenta", bold: true
7
+ Face.define :string, foreground: "blue", bold: true
8
+ end
@@ -96,7 +96,8 @@ module Textbringer
96
96
  GLOBAL_MAP.define_key(?\C-_, :undo)
97
97
  GLOBAL_MAP.define_key("\C-x\C-_", :redo)
98
98
  GLOBAL_MAP.define_key("\C-t", :transpose_chars)
99
- GLOBAL_MAP.define_key(?\n, :newline)
99
+ GLOBAL_MAP.define_key("\C-j", :newline)
100
+ GLOBAL_MAP.define_key("\C-m", :newline)
100
101
  GLOBAL_MAP.define_key("\C-l", :recenter)
101
102
  GLOBAL_MAP.define_key("\C-v", :scroll_up)
102
103
  GLOBAL_MAP.define_key(:npage, :scroll_up)
@@ -106,6 +107,7 @@ module Textbringer
106
107
  GLOBAL_MAP.define_key("\C-x1", :delete_other_windows)
107
108
  GLOBAL_MAP.define_key("\C-x2", :split_window)
108
109
  GLOBAL_MAP.define_key("\C-xo", :other_window)
110
+ GLOBAL_MAP.define_key("\C-x^", :enlarge_window)
109
111
  GLOBAL_MAP.define_key("\C-x\C-c", :exit_textbringer)
110
112
  GLOBAL_MAP.define_key("\C-z", :suspend_textbringer)
111
113
  GLOBAL_MAP.define_key("\C-x\C-f", :find_file)
@@ -113,8 +115,8 @@ module Textbringer
113
115
  GLOBAL_MAP.define_key("\C-x\C-s", :save_buffer)
114
116
  GLOBAL_MAP.define_key("\C-x\C-w", :write_file)
115
117
  GLOBAL_MAP.define_key("\C-xk", :kill_buffer)
116
- GLOBAL_MAP.define_key("\C-x\nf", :set_buffer_file_encoding)
117
- GLOBAL_MAP.define_key("\C-x\nn", :set_buffer_file_format)
118
+ GLOBAL_MAP.define_key("\C-x\C-mf", :set_buffer_file_encoding)
119
+ GLOBAL_MAP.define_key("\C-x\C-mn", :set_buffer_file_format)
118
120
  GLOBAL_MAP.define_key("\ex", :execute_command)
119
121
  GLOBAL_MAP.define_key("\e:", :eval_expression)
120
122
  GLOBAL_MAP.define_key(?\C-u, :universal_argument)
@@ -132,7 +134,8 @@ module Textbringer
132
134
  end
133
135
 
134
136
  MINIBUFFER_LOCAL_MAP = Keymap.new
135
- MINIBUFFER_LOCAL_MAP.define_key(?\n, :exit_recursive_edit)
137
+ MINIBUFFER_LOCAL_MAP.define_key("\C-j", :exit_recursive_edit)
138
+ MINIBUFFER_LOCAL_MAP.define_key("\C-m", :exit_recursive_edit)
136
139
  MINIBUFFER_LOCAL_MAP.define_key(?\t, :complete_minibuffer)
137
140
  MINIBUFFER_LOCAL_MAP.define_key(?\C-g, :abort_recursive_edit)
138
141
  end
@@ -17,6 +17,7 @@ module Textbringer
17
17
  attr_accessor :hook_name
18
18
  attr_accessor :file_name_pattern
19
19
  attr_accessor :interpreter_name_pattern
20
+ attr_reader :syntax_table
20
21
  end
21
22
 
22
23
  def self.define_generic_command(name)
@@ -35,6 +36,10 @@ module Textbringer
35
36
  end
36
37
  end
37
38
 
39
+ def self.define_syntax(face_name, re)
40
+ @syntax_table[face_name] = re
41
+ end
42
+
38
43
  def self.inherited(child)
39
44
  base_name = child.name.slice(/[^:]*\z/)
40
45
  child.mode_name = base_name.sub(/Mode\z/, "")
@@ -50,6 +55,7 @@ module Textbringer
50
55
  Buffer.current.apply_mode(child)
51
56
  end
52
57
  @@mode_list.push(child)
58
+ child.instance_variable_set(:@syntax_table, {})
53
59
  end
54
60
 
55
61
  attr_reader :buffer
@@ -61,5 +67,9 @@ module Textbringer
61
67
  def name
62
68
  self.class.mode_name
63
69
  end
70
+
71
+ def syntax_table
72
+ self.class.syntax_table
73
+ end
64
74
  end
65
75
  end
@@ -5,7 +5,9 @@ module Textbringer
5
5
  define_generic_command :jump_to_source_location
6
6
 
7
7
  BACKTRACE_MODE_MAP = Keymap.new
8
- BACKTRACE_MODE_MAP.define_key("\n", :jump_to_source_location_command)
8
+ BACKTRACE_MODE_MAP.define_key("\C-m", :jump_to_source_location_command)
9
+
10
+ define_syntax :link, /^\S*?:\d+:(?:\d+:)?/
9
11
 
10
12
  def initialize(buffer)
11
13
  super(buffer)
@@ -13,10 +15,11 @@ module Textbringer
13
15
  end
14
16
 
15
17
  def jump_to_source_location
16
- file_name, line_number = get_source_location
18
+ file_name, line_number, column_number = get_source_location
17
19
  if file_name
18
20
  find_file(file_name)
19
21
  goto_line(line_number)
22
+ forward_char(column_number - 1)
20
23
  end
21
24
  end
22
25
 
@@ -25,10 +28,11 @@ module Textbringer
25
28
  def get_source_location
26
29
  @buffer.save_excursion do
27
30
  @buffer.beginning_of_line
28
- if @buffer.looking_at?(/^(\S*?):(\d+):/)
31
+ if @buffer.looking_at?(/^(\S*?):(\d+):(?:(\d+):)?/)
29
32
  file_name = @buffer.match_string(1)
30
33
  line_number = @buffer.match_string(2).to_i
31
- [file_name, line_number]
34
+ column_number = (@buffer.match_string(3) || 1).to_i
35
+ [file_name, line_number, column_number]
32
36
  else
33
37
  nil
34
38
  end
@@ -10,6 +10,32 @@ module Textbringer
10
10
  class CMode < ProgrammingMode
11
11
  self.file_name_pattern = /\A.*\.[ch]\z/i
12
12
 
13
+ KEYWORDS = %w(
14
+ auto break case char const continue default double do
15
+ else enum extern float for goto if inline int long
16
+ register restrict return short signed sizeof static struct
17
+ switch typedef union unsigned void volatile while _Bool
18
+ _Complex _Imaginary
19
+ )
20
+
21
+ define_syntax :comment, /
22
+ (?: \/\* (?> (?:.|\n)*? \*\/ ) ) |
23
+ (?: \/\/ .*(?:\\\n.*)*(?:\z|(?<!\\)\n) )
24
+ /x
25
+
26
+ define_syntax :preprocessing_directive, /
27
+ ^ [\ \t]* (?: \# | %: ) [\ \t]* [_a-zA-Z][_a-zA-Z0-9]*
28
+ /x
29
+
30
+ define_syntax :keyword, /
31
+ \b (?: #{KEYWORDS.join("|")} ) \b
32
+ /x
33
+
34
+ define_syntax :string, /
35
+ (?: " (?: [^\\"] | \\ (?:.|\n) )* " ) |
36
+ (?: ' (?: [^\\'] | \\ (?:.|\n) )* ' )
37
+ /x
38
+
13
39
  def initialize(buffer)
14
40
  super(buffer)
15
41
  @buffer[:indent_level] = CONFIG[:c_indent_level]
@@ -18,7 +44,8 @@ module Textbringer
18
44
 
19
45
  def compile(cmd = read_from_minibuffer("Compile: ",
20
46
  default: default_compile_command))
21
- shell_execute(cmd, "*Compile result*")
47
+ shell_execute(cmd, buffer_name: "*Compile result*",
48
+ mode: BacktraceMode)
22
49
  end
23
50
 
24
51
  def symbol_pattern
@@ -55,13 +82,7 @@ module Textbringer
55
82
  (?<singleline_comment> \/\/ .*? \\\n (?:.|\n)* )
56
83
  ) |
57
84
  (?<keyword>
58
- (?:
59
- auto | break | case | char | const | continue | default | double | do |
60
- else | enum | extern | float | for | goto | if | inline | int | long |
61
- register | restrict | return | short | signed | sizeof | static | struct |
62
- switch | typedef | union | unsigned | void | volatile | while | _Bool |
63
- _Complex | _Imaginary
64
- ) \b
85
+ (?: #{KEYWORDS.join("|")} ) \b
65
86
  ) |
66
87
  (?<constant>
67
88
  (?<floating_constant>
@@ -7,6 +7,8 @@ module Textbringer
7
7
  COMPLETION_LIST_MODE_MAP = Keymap.new
8
8
  COMPLETION_LIST_MODE_MAP.define_key("\n", :choose_completion_command)
9
9
 
10
+ define_syntax :link, /^.+$/
11
+
10
12
  def initialize(buffer)
11
13
  super(buffer)
12
14
  buffer.keymap = COMPLETION_LIST_MODE_MAP
@@ -14,11 +14,11 @@ module Textbringer
14
14
 
15
15
  PROGRAMMING_MODE_MAP = Keymap.new
16
16
  PROGRAMMING_MODE_MAP.define_key("\t", :indent_line_command)
17
- PROGRAMMING_MODE_MAP.define_key("\n", :newline_and_reindent_command)
17
+ PROGRAMMING_MODE_MAP.define_key("\C-m", :newline_and_reindent_command)
18
18
  PROGRAMMING_MODE_MAP.define_key("\C-c\C-n", :forward_definition_command)
19
19
  PROGRAMMING_MODE_MAP.define_key("\C-c\C-p", :backward_definition_command)
20
20
  PROGRAMMING_MODE_MAP.define_key("\C-c\C-c", :compile_command)
21
- PROGRAMMING_MODE_MAP.define_key("\C-ct", :toggle_test_command)
21
+ PROGRAMMING_MODE_MAP.define_key("\C-c\C-t", :toggle_test_command)
22
22
 
23
23
  def initialize(buffer)
24
24
  super(buffer)
@@ -64,7 +64,7 @@ module Textbringer
64
64
  @buffer.save_excursion do
65
65
  pos = @buffer.point
66
66
  @buffer.beginning_of_line
67
- if /\A\s+\z/ =~ @buffer.substring(@buffer.point, pos)
67
+ if /\A[ \t]+\z/ =~ @buffer.substring(@buffer.point, pos)
68
68
  @buffer.delete_region(@buffer.point, pos)
69
69
  n += 1
70
70
  end
@@ -12,6 +12,65 @@ module Textbringer
12
12
  (?:Gem|Rake|Cap|Thor|Vagrant|Guard|Pod)file)\z/ix
13
13
  self.interpreter_name_pattern = /ruby/i
14
14
 
15
+ define_syntax :comment, /
16
+ (?: \#.*(?:\\\n.*)*(?:\z|(?<!\\)\n) ) |
17
+ (?: ^=begin (?:.|\n)* (?> ^=end \b ) )
18
+ /x
19
+
20
+ define_syntax :keyword, /
21
+ (?<![$@.]) \b (?: (?:
22
+ class | module | def | undef | begin | rescue | ensure | end |
23
+ if | unless | then | elsif | else | case | when | while | until |
24
+ for | break | next | redo | retry | in | do | return | yield |
25
+ super | self | nil | true | false | and | or | not | alias
26
+ ) \b | defined\? )
27
+ /x
28
+
29
+ define_syntax :string, /
30
+ (?: (?<! [a-zA-Z] ) \? (:? [^\\\s] | \\ . ) ) |
31
+ (?: %[qQrwWsix]?\{ (?: [^\\}] | \\ . )* \} ) |
32
+ (?: %[qQrwWsix]?\( (?: [^\\)] | \\ . )* \) ) |
33
+ (?: %[qQrwWsix]?\[ (?: [^\\\]] | \\ . )* \] ) |
34
+ (?: %[qQrwWsix]?< (?: [^\\>] | \\ . )* > ) |
35
+ (?:
36
+ %[qQrwWsix]?
37
+ (?<string_delimiter>[^{(\[<a-zA-Z0-9\s\u{0100}-\u{10ffff}])
38
+ (?: (?! \k<string_delimiter> ) [^\\] | \\ . )*
39
+ \k<string_delimiter>
40
+ ) |
41
+ (?:
42
+ (?<! \$ )
43
+ " (?: [^\\"] | \\ . )* "
44
+ ) |
45
+ (?:
46
+ (?<! \$ )
47
+ ' (?: [^\\'] | \\ . )* '
48
+ ) |
49
+ (?:
50
+ (?<! [$.] | def | def \s )
51
+ ` (?: [^\\`] | \\ . )* `
52
+ ) |
53
+ (?:
54
+ (?<=
55
+ ^ |
56
+ \b and | \b or | \b while | \b until | \b unless | \b if |
57
+ \b elsif | \b when | \b not | \b then | \b else |
58
+ [;~=!|&(,\[<>?:*+-]
59
+ ) \s*
60
+ \/ (?: [^\\\/] | \\ . )* \/[iomxneus]*
61
+ ) |
62
+ (?:
63
+ (?<! class | class \s | [\]})"'.] | :: | \w )
64
+ <<-?(?<heredoc_quote>['"`]?)
65
+ (?<heredoc_terminator>
66
+ [_a-zA-Z\u{0100}-\u{10ffff}]
67
+ [_a-zA-Z0-9\u{0100}-\u{10ffff}]*
68
+ )
69
+ \k<heredoc_quote>
70
+ (?> (?:.|\n)*? \k<heredoc_terminator> )
71
+ )
72
+ /x
73
+
15
74
  def initialize(buffer)
16
75
  super(buffer)
17
76
  @buffer[:indent_level] = CONFIG[:ruby_indent_level]
@@ -62,8 +121,8 @@ module Textbringer
62
121
 
63
122
  def compile(cmd = read_from_minibuffer("Compile: ",
64
123
  default: default_compile_command))
65
- shell_execute(cmd, "*Ruby compile result*")
66
- backtrace_mode
124
+ shell_execute(cmd, buffer_name: "*Ruby compile result*",
125
+ mode: BacktraceMode)
67
126
  end
68
127
 
69
128
  def symbol_pattern
@@ -176,7 +235,9 @@ module Textbringer
176
235
  e != :on_sp && e != :on_nl && e != :on_ignored_nl
177
236
  }
178
237
  if (last_event == :on_op && last_text != "|") ||
179
- last_event == :on_period
238
+ last_event == :on_period ||
239
+ (last_event == :on_comma &&
240
+ event != :on_lbrace && event != :on_lbracket)
180
241
  indentation += @buffer[:indent_level]
181
242
  end
182
243
  indentation
@@ -90,6 +90,7 @@ module Textbringer
90
90
  }
91
91
 
92
92
  def read_from_minibuffer(prompt, completion_proc: nil, default: nil,
93
+ initial_value: nil,
93
94
  keymap: MINIBUFFER_LOCAL_MAP)
94
95
  if Window.echo_area.active?
95
96
  raise EditorError,
@@ -104,9 +105,9 @@ module Textbringer
104
105
  Buffer.minibuffer[:completion_proc] = completion_proc
105
106
  Window.echo_area.active = true
106
107
  begin
107
- Buffer.minibuffer.delete_region(Buffer.minibuffer.point_min,
108
- Buffer.minibuffer.point_max)
109
108
  Window.current = Window.echo_area
109
+ Buffer.minibuffer.clear
110
+ Buffer.minibuffer.insert(initial_value) if initial_value
110
111
  if default
111
112
  prompt = prompt.sub(/:/, " (default #{default}):")
112
113
  end
@@ -275,8 +276,17 @@ module Textbringer
275
276
  RbConfig::CONFIG["ruby_install_name"]
276
277
  end
277
278
 
278
- def insert(s)
279
- Buffer.current.insert(s)
279
+ [
280
+ :beginning_of_buffer?,
281
+ :end_of_buffer?,
282
+ :beginning_of_line?,
283
+ :end_of_line?,
284
+ :insert,
285
+ :gsub
286
+ ].each do |name|
287
+ define_method(name) do |*args, &block|
288
+ Buffer.current.send(name, *args, &block)
289
+ end
280
290
  end
281
291
  end
282
292
  end
@@ -1,3 +1,3 @@
1
1
  module Textbringer
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -48,9 +48,11 @@ module Textbringer
48
48
  name.slice(/\AKEY_(.*)/, 1).downcase.intern
49
49
  end
50
50
 
51
+ @@started = false
51
52
  @@windows = []
52
53
  @@current = nil
53
54
  @@echo_area = nil
55
+ @@has_colors = false
54
56
 
55
57
  def self.windows
56
58
  @@windows
@@ -119,10 +121,42 @@ module Textbringer
119
121
  @@echo_area
120
122
  end
121
123
 
124
+ def self.has_colors=(value)
125
+ @@has_colors = value
126
+ end
127
+
128
+ def self.has_colors?
129
+ @@has_colors
130
+ end
131
+
132
+ def self.colors
133
+ Curses.colors
134
+ end
135
+
136
+ def self.set_default_colors(fg, bg)
137
+ Curses.assume_default_colors(Color[fg], Color[bg])
138
+ Window.redraw
139
+ end
140
+
141
+ def self.load_faces
142
+ require_relative "faces/basic"
143
+ require_relative "faces/programming"
144
+ end
145
+
122
146
  def self.start
147
+ if @@started
148
+ raise EditorError, "Already started"
149
+ end
123
150
  Curses.init_screen
124
151
  Curses.noecho
125
152
  Curses.raw
153
+ Curses.nonl
154
+ self.has_colors = Curses.has_colors?
155
+ if has_colors?
156
+ Curses.start_color
157
+ Curses.use_default_colors
158
+ load_faces
159
+ end
126
160
  begin
127
161
  window =
128
162
  Textbringer::Window.new(Window.lines - 1, Window.columns, 0, 0)
@@ -134,11 +168,18 @@ module Textbringer
134
168
  Buffer.minibuffer.keymap = MINIBUFFER_LOCAL_MAP
135
169
  @@echo_area.buffer = Buffer.minibuffer
136
170
  @@windows.push(@@echo_area)
171
+ @@started = true
137
172
  yield
138
173
  ensure
174
+ @@windows.each do |win|
175
+ win.delete
176
+ end
177
+ @@windows.clear
139
178
  Curses.echo
140
179
  Curses.noraw
180
+ Curses.nl
141
181
  Curses.close_screen
182
+ @@started = false
142
183
  end
143
184
  end
144
185
 
@@ -173,7 +214,8 @@ module Textbringer
173
214
 
174
215
  def self.resize
175
216
  @@windows.delete_if do |window|
176
- if !window.echo_area? && window.y > Window.lines - 4
217
+ if !window.echo_area? &&
218
+ window.y > Window.lines - CONFIG[:window_min_height]
177
219
  window.delete
178
220
  true
179
221
  else
@@ -284,7 +326,7 @@ module Textbringer
284
326
  end
285
327
  KEY_NAMES[key] || key
286
328
  else
287
- key&.encode(Encoding::UTF_8)&.tr("\r", "\n")
329
+ key&.encode(Encoding::UTF_8)
288
330
  end
289
331
  end
290
332
 
@@ -329,6 +371,40 @@ module Textbringer
329
371
  end
330
372
  end
331
373
 
374
+ def highlight
375
+ @highlight_on = {}
376
+ @highlight_off = {}
377
+ return if !@@has_colors || !CONFIG[:syntax_highlight]
378
+ syntax_table = @buffer.mode.syntax_table
379
+ return if syntax_table.empty?
380
+ if @buffer.bytesize < CONFIG[:highlight_buffer_size_limit]
381
+ base_pos = @buffer.point_min
382
+ s = @buffer.to_s
383
+ else
384
+ base_pos = @buffer.point
385
+ len = columns * (lines - 1) / 2 * 3
386
+ s = @buffer.substring(@buffer.point, @buffer.point + len).scrub("")
387
+ end
388
+ re_str = syntax_table.map { |name, re|
389
+ "(?<#{name}>#{re})"
390
+ }.join("|")
391
+ re = Regexp.new(re_str)
392
+ names = syntax_table.keys
393
+ s.scan(re) do
394
+ b = base_pos + $`.bytesize
395
+ e = b + $&.bytesize
396
+ if b < @buffer.point && @buffer.point < e
397
+ b = @buffer.point
398
+ end
399
+ name = names.find { |n| $~[n] }
400
+ attributes = Face[name]&.attributes
401
+ if attributes
402
+ @highlight_on[b] = attributes
403
+ @highlight_off[e] = attributes
404
+ end
405
+ end
406
+ end
407
+
332
408
  def redisplay
333
409
  return if @buffer.nil?
334
410
  redisplay_mode_line
@@ -342,8 +418,10 @@ module Textbringer
342
418
  framer
343
419
  y = x = 0
344
420
  @buffer.point_to_mark(@top_of_window)
421
+ highlight
345
422
  @window.erase
346
423
  @window.setpos(0, 0)
424
+ @window.attrset(0)
347
425
  if current? && @buffer.visible_mark &&
348
426
  @buffer.point_after_mark?(@buffer.visible_mark)
349
427
  @window.attron(Curses::A_REVERSE)
@@ -368,6 +446,12 @@ module Textbringer
368
446
  @window.attron(Curses::A_REVERSE)
369
447
  end
370
448
  end
449
+ if attrs = @highlight_off[@buffer.point]
450
+ @window.attroff(attrs)
451
+ end
452
+ if attrs = @highlight_on[@buffer.point]
453
+ @window.attron(attrs)
454
+ end
371
455
  c = @buffer.char_after
372
456
  if c == "\n"
373
457
  @window.clrtoeol
@@ -478,11 +562,11 @@ module Textbringer
478
562
  end
479
563
 
480
564
  def split
481
- if lines < 6
482
- raise EditorError, "Window too small"
483
- end
484
565
  old_lines = lines
485
566
  new_lines = (old_lines / 2.0).ceil
567
+ if new_lines < CONFIG[:window_min_height]
568
+ raise EditorError, "Window too small"
569
+ end
486
570
  resize(new_lines, columns)
487
571
  new_window = Window.new(old_lines - new_lines, columns, y + new_lines, x)
488
572
  new_window.buffer = buffer
@@ -490,6 +574,47 @@ module Textbringer
490
574
  @@windows.insert(i + 1, new_window)
491
575
  end
492
576
 
577
+ def enlarge(n)
578
+ if n > 0
579
+ max_height = Window.lines -
580
+ CONFIG[:window_min_height] * (@@windows.size - 2) - 1
581
+ new_lines = [lines + n, max_height].min
582
+ needed_lines = new_lines - lines
583
+ resize(new_lines, columns)
584
+ i = @@windows.index(self)
585
+ indices = (i + 1).upto(@@windows.size - 2).to_a +
586
+ (i - 1).downto(0).to_a
587
+ indices.each do |j|
588
+ break if needed_lines == 0
589
+ window = @@windows[j]
590
+ extended_lines = [
591
+ window.lines - CONFIG[:window_min_height],
592
+ needed_lines
593
+ ].min
594
+ window.resize(window.lines - extended_lines, window.columns)
595
+ needed_lines -= extended_lines
596
+ end
597
+ y = 0
598
+ @@windows.each do |win|
599
+ win.move(y, win.x)
600
+ y += win.lines
601
+ end
602
+ elsif n < 0 && @@windows.size > 2
603
+ new_lines = [lines + n, CONFIG[:window_min_height]].max
604
+ diff = lines - new_lines
605
+ resize(new_lines, columns)
606
+ i = @@windows.index(self)
607
+ if i < @@windows.size - 2
608
+ window = @@windows[i + 1]
609
+ window.move(window.y - diff, window.x)
610
+ else
611
+ window = @@windows[i - 1]
612
+ move(self.y + diff, self.x)
613
+ end
614
+ window.resize(window.lines + diff, window.columns)
615
+ end
616
+ end
617
+
493
618
  private
494
619
 
495
620
  def initialize_window(num_lines, num_columns, y, x)
@@ -522,7 +647,8 @@ module Textbringer
522
647
  def redisplay_mode_line
523
648
  @mode_line.erase
524
649
  @mode_line.setpos(0, 0)
525
- @mode_line.attron(Curses::A_REVERSE)
650
+ attrs = @@has_colors ? Face[:mode_line].attributes : Curses::A_REVERSE
651
+ @mode_line.attrset(attrs)
526
652
  @mode_line.addstr("#{@buffer.name} ")
527
653
  @mode_line.addstr("[+]") if @buffer.modified?
528
654
  @mode_line.addstr("[RO]") if @buffer.read_only?
@@ -540,7 +666,7 @@ module Textbringer
540
666
  @mode_line.addstr(" #{line},#{column}")
541
667
  @mode_line.addstr(" (#{@buffer.mode&.name || 'None'})")
542
668
  @mode_line.addstr(" " * (columns - @mode_line.curx))
543
- @mode_line.attroff(Curses::A_REVERSE)
669
+ @mode_line.attrset(0)
544
670
  @mode_line.noutrefresh
545
671
  end
546
672
 
@@ -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 "curses", "~> 1.1"
24
+ spec.add_runtime_dependency "curses", "~> 1.2"
25
25
  spec.add_runtime_dependency "unicode-display_width", "~> 1.1"
26
26
  spec.add_runtime_dependency "clipboard", "~> 1.1"
27
27
  spec.add_runtime_dependency "ffi"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: textbringer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
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-02-12 00:00:00.000000000 Z
11
+ date: 2017-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.1'
19
+ version: '1.2'
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.1'
26
+ version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: unicode-display_width
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -229,6 +229,7 @@ files:
229
229
  - exe/textbringer
230
230
  - lib/textbringer.rb
231
231
  - lib/textbringer/buffer.rb
232
+ - lib/textbringer/color.rb
232
233
  - lib/textbringer/commands.rb
233
234
  - lib/textbringer/commands/buffers.rb
234
235
  - lib/textbringer/commands/clipboard.rb
@@ -242,6 +243,9 @@ files:
242
243
  - lib/textbringer/config.rb
243
244
  - lib/textbringer/controller.rb
244
245
  - lib/textbringer/errors.rb
246
+ - lib/textbringer/face.rb
247
+ - lib/textbringer/faces/basic.rb
248
+ - lib/textbringer/faces/programming.rb
245
249
  - lib/textbringer/keymap.rb
246
250
  - lib/textbringer/mode.rb
247
251
  - lib/textbringer/modes/backtrace_mode.rb