vice-editor 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +10 -0
- data/Rakefile +24 -0
- data/bin/vice +6 -0
- data/lib/vice.rb +82 -0
- data/lib/vice/blitter.rb +133 -0
- data/lib/vice/buffer.rb +154 -0
- data/lib/vice/cursor.rb +8 -0
- data/lib/vice/movement.rb +54 -0
- data/lib/vice/parser.rb +188 -0
- data/lib/vice/prompt.rb +32 -0
- data/lib/vice/version.rb +3 -0
- data/test/vice.rb +37 -0
- data/test/vice/buffer.rb +82 -0
- data/test/vice/movement.rb +41 -0
- data/test/vice/parser.rb +81 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d298389b60730b766f83fd18d71e7102437370fd
|
4
|
+
data.tar.gz: 46b702832f8a6a80c84f1a73782c227dde7528ac
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0c77bab922bb385e70adae409916f475dfac1f30fe607bd92a8245b16eec1bb2f3156cdf812d1f8dc2a3bd3a77a0919df8c235279e7c25905592054f230f319d
|
7
|
+
data.tar.gz: c39f48642fd406ac1edd9816bf07d87b931c3c7a96301400108b5f7b44c07c7fbf625173848155bbbe7f81ba91d960a8654a1a2a6fff4f58ba40071b1d93536a
|
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# vice
|
2
|
+
[![Build Status](https://travis-ci.org/knarka/vice.svg?branch=master)](https://travis-ci.org/knarka/vice)
|
3
|
+
|
4
|
+
vice (**vi**sual **c**ode **e**ditor) is a small vi-like editor. It is currently still a work in progress.
|
5
|
+
|
6
|
+
## Running
|
7
|
+
Execute `rake`.
|
8
|
+
|
9
|
+
## Running the tests
|
10
|
+
Execute `rake test`.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
task default: %w[run]
|
2
|
+
|
3
|
+
task :run do
|
4
|
+
require_relative 'lib/vice'
|
5
|
+
Vice::Vice.new(nil).start
|
6
|
+
end
|
7
|
+
|
8
|
+
task :test do
|
9
|
+
Dir.foreach('test/vice') do |test|
|
10
|
+
next if ['.', '..'].include? test
|
11
|
+
ruby 'test/vice/' + test
|
12
|
+
end
|
13
|
+
ruby 'test/vice.rb'
|
14
|
+
end
|
15
|
+
|
16
|
+
task :style do
|
17
|
+
sh 'rubocop -c .rubocop.yml lib/ test/ Gemfile Rakefile'
|
18
|
+
end
|
19
|
+
|
20
|
+
task :clean do
|
21
|
+
Dir.glob('.').select{ |f| /.*\.gem$/.match f }.each do |file|
|
22
|
+
File.delete file
|
23
|
+
end
|
24
|
+
end
|
data/bin/vice
ADDED
data/lib/vice.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module Vice
|
4
|
+
TAB_WIDTH = 4
|
5
|
+
|
6
|
+
class Vice
|
7
|
+
attr_accessor :buffers
|
8
|
+
attr_accessor :current_buffer
|
9
|
+
attr_accessor :cursor
|
10
|
+
attr_accessor :mode
|
11
|
+
attr_accessor :prompt
|
12
|
+
|
13
|
+
attr_reader :msg
|
14
|
+
|
15
|
+
def initialize(filenames)
|
16
|
+
@mode = :command
|
17
|
+
@buffers = []
|
18
|
+
@parser = Parser.new
|
19
|
+
@prompt = ''
|
20
|
+
|
21
|
+
@current_buffer = 0
|
22
|
+
if filenames.nil? || filenames.empty?
|
23
|
+
@buffers.push Buffer.new nil
|
24
|
+
else
|
25
|
+
filenames.each do |f|
|
26
|
+
@buffers.push Buffer.new(f)
|
27
|
+
next_buffer
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
Curses.init_screen
|
34
|
+
Curses.noecho
|
35
|
+
Curses.start_color
|
36
|
+
Curses.use_default_colors
|
37
|
+
window = Curses.stdscr
|
38
|
+
|
39
|
+
blitter = Blitter.new window
|
40
|
+
|
41
|
+
alert "welcome to vice #{VERSION} - https://github.com/knarka/vice"
|
42
|
+
|
43
|
+
loop do
|
44
|
+
blitter.drawbuffer self, window
|
45
|
+
key = window.getch
|
46
|
+
@parser.parsekeypress self, @current_buffer, key
|
47
|
+
end
|
48
|
+
|
49
|
+
Curses.close_screen
|
50
|
+
end
|
51
|
+
|
52
|
+
def alert(msg)
|
53
|
+
@msg = msg
|
54
|
+
end
|
55
|
+
|
56
|
+
def error(msg)
|
57
|
+
@msg = 'error: ' + msg
|
58
|
+
end
|
59
|
+
|
60
|
+
def reset_alert
|
61
|
+
@msg = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def next_buffer
|
65
|
+
@current_buffer += 1
|
66
|
+
@current_buffer = 0 if @current_buffer >= @buffers.length
|
67
|
+
end
|
68
|
+
|
69
|
+
def prev_buffer
|
70
|
+
@current_buffer -= 1
|
71
|
+
@current_buffer = @buffers.length - 1 if @current_buffer < 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
require_relative 'vice/version'
|
77
|
+
require_relative 'vice/buffer'
|
78
|
+
require_relative 'vice/cursor'
|
79
|
+
require_relative 'vice/parser'
|
80
|
+
require_relative 'vice/blitter'
|
81
|
+
require_relative 'vice/movement'
|
82
|
+
require_relative 'vice/prompt'
|
data/lib/vice/blitter.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
class Vice::Blitter
|
2
|
+
def initialize(window)
|
3
|
+
Curses.init_pair 1, Curses::COLOR_BLACK, Curses::COLOR_WHITE
|
4
|
+
window.attrset Curses.color_pair(0)
|
5
|
+
end
|
6
|
+
|
7
|
+
def drawtabs(vice, buffer, window)
|
8
|
+
window.attrset Curses.color_pair(1)
|
9
|
+
window.setpos 0, 0
|
10
|
+
window.addstr ' ' * Curses.cols
|
11
|
+
window.setpos 0, 2
|
12
|
+
vice.buffers.each do |b|
|
13
|
+
window.attrset(Curses.color_pair(0) | Curses::A_BOLD) if b == buffer
|
14
|
+
|
15
|
+
name = b.filename || '[no name]'
|
16
|
+
name = b.modified ? '+ ' + name : name
|
17
|
+
|
18
|
+
window.addstr ' ' + name + ' '
|
19
|
+
|
20
|
+
window.attrset Curses.color_pair(1) if b == buffer
|
21
|
+
end
|
22
|
+
window.attrset Curses.color_pair(0)
|
23
|
+
end
|
24
|
+
|
25
|
+
def drawalert(vice, window)
|
26
|
+
window.setpos Curses.lines - 1, 0
|
27
|
+
window.addstr ' ' * Curses.cols
|
28
|
+
|
29
|
+
return if vice.msg.nil?
|
30
|
+
|
31
|
+
window.setpos Curses.lines - 1, 0
|
32
|
+
window.addstr ' ' + vice.msg
|
33
|
+
|
34
|
+
vice.reset_alert
|
35
|
+
end
|
36
|
+
|
37
|
+
def drawstatus(mode, window, buffer, prompt)
|
38
|
+
window.attrset Curses.color_pair(1)
|
39
|
+
|
40
|
+
# clear
|
41
|
+
window.setpos Curses.lines - 2, 0
|
42
|
+
window.addstr ' ' * Curses.cols
|
43
|
+
|
44
|
+
# draw mode
|
45
|
+
window.setpos Curses.lines - 2, 0
|
46
|
+
modestring = if mode == :insert
|
47
|
+
' -- insert --'
|
48
|
+
elsif mode == :prompt
|
49
|
+
' :' + prompt
|
50
|
+
end
|
51
|
+
window.addstr modestring
|
52
|
+
|
53
|
+
# draw cursor position
|
54
|
+
location = buffer.cursor.line.to_s + ',' + buffer.cursor.col.to_s
|
55
|
+
window.setpos Curses.lines - 2, Curses.cols - location.length - 1
|
56
|
+
window.addstr location
|
57
|
+
|
58
|
+
window.attrset Curses.color_pair(0)
|
59
|
+
end
|
60
|
+
|
61
|
+
def formatnumber(number)
|
62
|
+
delta = if number == '~'
|
63
|
+
@linenumwidth - 1
|
64
|
+
else
|
65
|
+
@linenumwidth - number.to_s.length
|
66
|
+
end
|
67
|
+
delta.times do
|
68
|
+
number = ' ' + number.to_s
|
69
|
+
end
|
70
|
+
number += ' '
|
71
|
+
end
|
72
|
+
|
73
|
+
def pad(string)
|
74
|
+
delta = Curses.cols - string.length
|
75
|
+
delta.times { string += ' ' } if delta > 0
|
76
|
+
string
|
77
|
+
end
|
78
|
+
|
79
|
+
def drawbuffer(vice, window)
|
80
|
+
buffer = vice.buffers[vice.current_buffer]
|
81
|
+
visual_cursor = Vice::Cursor.new buffer.cursor.line, 0
|
82
|
+
|
83
|
+
(0..buffer.cursor.col - 1).each do |i|
|
84
|
+
visual_cursor.col += if buffer.currentline[i] == "\t"
|
85
|
+
Vice::TAB_WIDTH
|
86
|
+
else
|
87
|
+
1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
drawtabs vice, buffer, window
|
92
|
+
|
93
|
+
@linenumwidth = buffer.lines.to_s.length + 1
|
94
|
+
|
95
|
+
visual_cursor.line += 1 # space for tabs above buffer
|
96
|
+
visual_cursor.line -= buffer.v_scroll
|
97
|
+
visual_cursor.col += @linenumwidth + 1 # leave space for line numbers
|
98
|
+
|
99
|
+
while visual_cursor.line >= Curses.lines - 2
|
100
|
+
buffer.v_scroll += 1
|
101
|
+
visual_cursor.line -= 1
|
102
|
+
end
|
103
|
+
|
104
|
+
while visual_cursor.line < 1
|
105
|
+
buffer.v_scroll -= 1
|
106
|
+
visual_cursor.line += 1
|
107
|
+
end
|
108
|
+
|
109
|
+
(1..Curses.lines - 2).each do |r|
|
110
|
+
# r: on-screen line
|
111
|
+
# i: in-buffer line
|
112
|
+
i = r - 1 + buffer.v_scroll
|
113
|
+
window.setpos r, 0
|
114
|
+
if i < buffer.lines
|
115
|
+
line = pad buffer.getline(i).gsub(/(\t)/, ' ' * Vice::TAB_WIDTH)
|
116
|
+
window.addstr formatnumber(i + 1) + line
|
117
|
+
else
|
118
|
+
window.addstr pad(formatnumber('~'))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
drawstatus vice.mode, window, buffer, vice.prompt
|
123
|
+
|
124
|
+
drawalert vice, window
|
125
|
+
|
126
|
+
if vice.mode == :prompt
|
127
|
+
window.setpos Curses.lines - 2, vice.prompt.length + 2
|
128
|
+
else
|
129
|
+
window.setpos visual_cursor.line, visual_cursor.col
|
130
|
+
end
|
131
|
+
window.refresh
|
132
|
+
end
|
133
|
+
end
|
data/lib/vice/buffer.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
class Vice::Buffer
|
2
|
+
attr_reader :buffer
|
3
|
+
attr_reader :filename
|
4
|
+
attr_reader :modified
|
5
|
+
attr_accessor :cursor
|
6
|
+
attr_accessor :v_scroll
|
7
|
+
|
8
|
+
def initialize(filename)
|
9
|
+
@buffer = []
|
10
|
+
@marks = {}
|
11
|
+
if filename
|
12
|
+
@filename = filename
|
13
|
+
File.open(filename, 'r') do |f| # TODO: don't assume file exists
|
14
|
+
f.each_line { |line| @buffer.push line.chomp }
|
15
|
+
end
|
16
|
+
else
|
17
|
+
@buffer.push ''
|
18
|
+
end
|
19
|
+
@cursor = Vice::Cursor.new
|
20
|
+
@modified = false
|
21
|
+
@v_scroll = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def writef(filename)
|
25
|
+
@modified = false
|
26
|
+
|
27
|
+
File.open(filename, 'w') do |f|
|
28
|
+
f.write @buffer.join "\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def write
|
33
|
+
writef @filename
|
34
|
+
end
|
35
|
+
|
36
|
+
def cursor_end_of_line
|
37
|
+
# if we're out of bounds, move the cursor to the end of the line
|
38
|
+
@cursor.col = @buffer[@cursor.line].length - 1 if @cursor.col >= @buffer[@cursor.line].length
|
39
|
+
@cursor.col = 0 if @cursor.col < 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def cursor_up
|
43
|
+
@cursor.line -= 1 if @cursor.line > 0
|
44
|
+
cursor_end_of_line
|
45
|
+
end
|
46
|
+
|
47
|
+
def cursor_down
|
48
|
+
@cursor.line += 1 if @cursor.line < @buffer.length - 1
|
49
|
+
cursor_end_of_line
|
50
|
+
end
|
51
|
+
|
52
|
+
def cursor_left
|
53
|
+
@cursor.col -= 1 if @cursor.col > 0
|
54
|
+
end
|
55
|
+
|
56
|
+
def cursor_right
|
57
|
+
@cursor.col += 1 if @cursor.col < @buffer[@cursor.line].length - 1
|
58
|
+
end
|
59
|
+
|
60
|
+
def newline(index)
|
61
|
+
raise 'negative line index' unless index >= 0
|
62
|
+
|
63
|
+
@modified = true
|
64
|
+
|
65
|
+
# silently append to the end when index out of bounds
|
66
|
+
index = @buffer.length if index > @buffer.length
|
67
|
+
|
68
|
+
@buffer.insert index, ''
|
69
|
+
end
|
70
|
+
|
71
|
+
def rmlinef(index)
|
72
|
+
raise 'negative line index' unless index >= 0
|
73
|
+
raise 'line index out of bounds' unless index < @buffer.length
|
74
|
+
|
75
|
+
@modified = true
|
76
|
+
|
77
|
+
@buffer.delete_at index
|
78
|
+
end
|
79
|
+
|
80
|
+
def rmline
|
81
|
+
rmlinef @cursor.line
|
82
|
+
end
|
83
|
+
|
84
|
+
def insertf(index, column, text)
|
85
|
+
raise 'negative line index' unless index >= 0
|
86
|
+
raise 'line index out of bounds' unless index < @buffer.length
|
87
|
+
raise 'negative column index' unless column >= 0
|
88
|
+
raise 'column index out of bounds' unless column <= @buffer[index].length
|
89
|
+
|
90
|
+
@modified = true
|
91
|
+
|
92
|
+
@buffer[index].insert column, text
|
93
|
+
end
|
94
|
+
|
95
|
+
def insert(text)
|
96
|
+
insertf @cursor.line, @cursor.col, text
|
97
|
+
end
|
98
|
+
|
99
|
+
def rmcharf(index, column)
|
100
|
+
raise 'negative line index' unless index >= 0
|
101
|
+
raise 'line index out of bounds' unless index < @buffer.length
|
102
|
+
raise 'negative column index' unless column >= 0
|
103
|
+
raise 'column index out of bounds' unless column <= @buffer[index].length
|
104
|
+
|
105
|
+
@modified = true
|
106
|
+
|
107
|
+
@buffer[index].slice! column
|
108
|
+
end
|
109
|
+
|
110
|
+
def rmchar
|
111
|
+
rmcharf @cursor.line, @cursor.col
|
112
|
+
end
|
113
|
+
|
114
|
+
def setline(index, text)
|
115
|
+
raise 'negative line index' unless index >= 0
|
116
|
+
raise 'line index out of bounds' unless index < @buffer.length
|
117
|
+
|
118
|
+
@modified = true
|
119
|
+
|
120
|
+
@buffer[index] = text
|
121
|
+
cursor_end_of_line
|
122
|
+
end
|
123
|
+
|
124
|
+
def getline(index)
|
125
|
+
raise 'negative line index' unless index >= 0
|
126
|
+
raise 'line index out of bounds' unless index < @buffer.length
|
127
|
+
|
128
|
+
@buffer[index]
|
129
|
+
end
|
130
|
+
|
131
|
+
def currentline
|
132
|
+
getline @cursor.line
|
133
|
+
end
|
134
|
+
|
135
|
+
def lines
|
136
|
+
@buffer.length
|
137
|
+
end
|
138
|
+
|
139
|
+
def cols
|
140
|
+
@buffer[@cursor.line].length
|
141
|
+
end
|
142
|
+
|
143
|
+
def addmark(mark)
|
144
|
+
@marks[mark] = @cursor.clone
|
145
|
+
end
|
146
|
+
|
147
|
+
def gotomark(mark)
|
148
|
+
return false unless @marks[mark]
|
149
|
+
@cursor = @marks[mark].clone
|
150
|
+
@cursor.line = @buffer.length - 1 if @cursor.line >= @buffer.length
|
151
|
+
cursor_end_of_line
|
152
|
+
true
|
153
|
+
end
|
154
|
+
end
|
data/lib/vice/cursor.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
class Vice::Movement
|
2
|
+
def self.whitespace(char, harsh)
|
3
|
+
if harsh # only real whitespace
|
4
|
+
(char == ' ' || char == "\t" || char == "\n")
|
5
|
+
else # anything that's not alphanumeric
|
6
|
+
char !~ /\A\p{Alnum}+\z/
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.w_real(line, start, harsh)
|
11
|
+
return start if start == line.length - 1
|
12
|
+
return start + 1 if whitespace(line[start], harsh) && !whitespace(line[start + 1], harsh)
|
13
|
+
w_real(line, start + 1, harsh)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.w(line, start)
|
17
|
+
w_real(line, start, false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.w_large(line, start)
|
21
|
+
w_real(line, start, true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.b_internal(line, start, harsh)
|
25
|
+
return start if start.zero?
|
26
|
+
|
27
|
+
return start if !whitespace(line[start], harsh) && whitespace(line[start - 1], harsh)
|
28
|
+
|
29
|
+
b_internal(line, start - 1, harsh)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.b_real(line, start, harsh)
|
33
|
+
# if we're already on the beginning of a word, we jump to the previous one
|
34
|
+
start -= 1 if start > 0 && !whitespace(line[start], harsh) && whitespace(line[start - 1], harsh)
|
35
|
+
|
36
|
+
b_internal(line, start, harsh)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.b(line, start)
|
40
|
+
b_real(line, start, false)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.b_large(line, start)
|
44
|
+
b_real(line, start, true)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.dollar(line)
|
48
|
+
line.length - 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.zero
|
52
|
+
0
|
53
|
+
end
|
54
|
+
end
|
data/lib/vice/parser.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
class Vice::Parser
|
2
|
+
def initialize
|
3
|
+
resettrail
|
4
|
+
end
|
5
|
+
|
6
|
+
def resettrail
|
7
|
+
@trail = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def parsekeypress(vice, buffer, char)
|
11
|
+
if vice.mode == :command
|
12
|
+
parsechar_command vice, buffer, char
|
13
|
+
elsif vice.mode == :insert
|
14
|
+
parsechar_insert vice, buffer, char
|
15
|
+
elsif vice.mode == :prompt
|
16
|
+
parsechar_prompt vice, buffer, char
|
17
|
+
else
|
18
|
+
raise 'parsekeypress called with unknown mode'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def parsechar_prompt(vice, current_buffer, char)
|
23
|
+
case char
|
24
|
+
when 10
|
25
|
+
Vice::Prompt.parse vice, vice.prompt, vice.buffers[current_buffer]
|
26
|
+
vice.prompt = ''
|
27
|
+
vice.mode = :command
|
28
|
+
when 27
|
29
|
+
vice.prompt = ''
|
30
|
+
vice.mode = :command
|
31
|
+
when Integer
|
32
|
+
vice.prompt += char if char >= 0 && char <= 10
|
33
|
+
else
|
34
|
+
vice.prompt += char
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def parsechar_command(vice, current_buffer, char)
|
39
|
+
buffer = vice.buffers[current_buffer]
|
40
|
+
raise 'parsechar called from command mode' unless vice.mode == :command
|
41
|
+
|
42
|
+
if @trail == ['m']
|
43
|
+
buffer.addmark char
|
44
|
+
vice.alert "added mark '" + char + "'"
|
45
|
+
@trail = []
|
46
|
+
return
|
47
|
+
elsif @trail == ["'"]
|
48
|
+
vice.alert 'mark not set' unless buffer.gotomark char
|
49
|
+
@trail = []
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
53
|
+
case char
|
54
|
+
# movement
|
55
|
+
when 'j'
|
56
|
+
buffer.cursor_down
|
57
|
+
when 'k'
|
58
|
+
buffer.cursor_up
|
59
|
+
when 'l'
|
60
|
+
buffer.cursor_right
|
61
|
+
when 'h'
|
62
|
+
buffer.cursor_left
|
63
|
+
when 'w'
|
64
|
+
if !@trail.empty?
|
65
|
+
if (@trail[0] == 'd' || @trail[0] == 'c') && buffer.currentline != ''
|
66
|
+
line_edited = buffer.currentline
|
67
|
+
slice_start = buffer.cursor.col
|
68
|
+
slice_end = Vice::Movement.w(buffer.currentline, buffer.cursor.col)
|
69
|
+
amount = slice_end - buffer.cursor.col
|
70
|
+
amount += 1 if slice_end == buffer.currentline.length - 1
|
71
|
+
line_edited.slice! slice_start, amount
|
72
|
+
|
73
|
+
buffer.setline buffer.cursor.line, line_edited
|
74
|
+
end
|
75
|
+
vice.mode = :insert if @trail[0] == 'c'
|
76
|
+
@trail = []
|
77
|
+
else
|
78
|
+
buffer.cursor.col = Vice::Movement.w(buffer.currentline, buffer.cursor.col)
|
79
|
+
end
|
80
|
+
when 'W'
|
81
|
+
buffer.cursor.col = Vice::Movement.W(buffer.currentline, buffer.cursor.col)
|
82
|
+
when 'b'
|
83
|
+
buffer.cursor.col = Vice::Movement.b(buffer.currentline, buffer.cursor.col)
|
84
|
+
when 'B'
|
85
|
+
buffer.cursor.col = Vice::Movement.B(buffer.currentline, buffer.cursor.col)
|
86
|
+
when '$'
|
87
|
+
buffer.cursor.col = Vice::Movement.dollar buffer.currentline
|
88
|
+
when '0'
|
89
|
+
buffer.cursor.col = Vice::Movement.zero
|
90
|
+
|
91
|
+
# entering insert mode
|
92
|
+
when 'I'
|
93
|
+
buffer.cursor.col = 0
|
94
|
+
vice.mode = :insert
|
95
|
+
when 'i'
|
96
|
+
vice.mode = :insert
|
97
|
+
when 'A'
|
98
|
+
buffer.cursor.col = buffer.cols
|
99
|
+
vice.mode = :insert
|
100
|
+
when 'a'
|
101
|
+
buffer.cursor.col += 1 if buffer.cursor.col < buffer.cols
|
102
|
+
vice.mode = :insert
|
103
|
+
when 'O'
|
104
|
+
buffer.newline buffer.cursor.line
|
105
|
+
buffer.cursor.col = 0
|
106
|
+
vice.mode = :insert
|
107
|
+
when 'o'
|
108
|
+
buffer.newline buffer.cursor.line + 1
|
109
|
+
buffer.cursor_down
|
110
|
+
vice.mode = :insert
|
111
|
+
|
112
|
+
# etc.
|
113
|
+
when 'x'
|
114
|
+
buffer.rmchar
|
115
|
+
buffer.cursor.col -= 1 if buffer.cursor.col > 0
|
116
|
+
when 'c'
|
117
|
+
if !@trail.empty? && @trail[0] == 'c'
|
118
|
+
buffer.setline buffer.cursor.line, ''
|
119
|
+
buffer.cursor.col = 0
|
120
|
+
vice.mode = :insert
|
121
|
+
@trail = []
|
122
|
+
else
|
123
|
+
@trail.push 'c'
|
124
|
+
end
|
125
|
+
when 'd'
|
126
|
+
if !@trail.empty? && @trail[0] == 'd'
|
127
|
+
if buffer.lines == 1
|
128
|
+
buffer.setline 0, ''
|
129
|
+
else
|
130
|
+
buffer.rmline
|
131
|
+
end
|
132
|
+
buffer.cursor.line -= 1 if buffer.lines <= buffer.cursor.line
|
133
|
+
buffer.cursor.col = 0
|
134
|
+
@trail = []
|
135
|
+
else
|
136
|
+
@trail.push 'd'
|
137
|
+
end
|
138
|
+
when 'g'
|
139
|
+
if !@trail.empty? && @trail[0] == 'g'
|
140
|
+
buffer.cursor.line = 0
|
141
|
+
buffer.cursor.col = 0
|
142
|
+
else
|
143
|
+
@trail.push 'g'
|
144
|
+
end
|
145
|
+
when 'G'
|
146
|
+
buffer.cursor.line = buffer.lines - 1
|
147
|
+
buffer.cursor.col = 0
|
148
|
+
when 'm'
|
149
|
+
@trail.push 'm' if @trail.empty?
|
150
|
+
when 't'
|
151
|
+
vice.next_buffer if !@trail.empty? && @trail[0] == 'g'
|
152
|
+
@trail = []
|
153
|
+
when 'T'
|
154
|
+
vice.prev_buffer if !@trail.empty? && @trail[0] == 'g'
|
155
|
+
@trail = []
|
156
|
+
when ';', ':'
|
157
|
+
vice.mode = :prompt
|
158
|
+
when "'"
|
159
|
+
@trail.push "'" if @trail.empty?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def parsechar_insert(vice, current_buffer, char)
|
164
|
+
buffer = vice.buffers[current_buffer]
|
165
|
+
raise 'insertchar called from insert mode' unless vice.mode == :insert
|
166
|
+
case char
|
167
|
+
when 9
|
168
|
+
buffer.insert "\t"
|
169
|
+
buffer.cursor.col += 1
|
170
|
+
when 10 # return
|
171
|
+
buffer.newline buffer.cursor.line + 1
|
172
|
+
buffer.cursor_down
|
173
|
+
when 27 # escape
|
174
|
+
vice.mode = :command
|
175
|
+
buffer.cursor_end_of_line
|
176
|
+
when 127 # backspace
|
177
|
+
if buffer.cursor.col > 0
|
178
|
+
buffer.cursor.col -= 1
|
179
|
+
buffer.rmchar
|
180
|
+
end
|
181
|
+
when Integer
|
182
|
+
vice.prompt += char if char >= 0 && char <= 10
|
183
|
+
else
|
184
|
+
buffer.insert char
|
185
|
+
buffer.cursor.col += 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
data/lib/vice/prompt.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Vice::Prompt
|
2
|
+
def self.parse(vice, line, buffer)
|
3
|
+
words = line.split ' '
|
4
|
+
return if words.empty?
|
5
|
+
|
6
|
+
case words[0]
|
7
|
+
when 'e', 't'
|
8
|
+
if words.length > 1
|
9
|
+
files = words.dup
|
10
|
+
files.shift
|
11
|
+
vice.buffers.delete buffer unless buffer.modified || words[0] == 't'
|
12
|
+
files.each do |f|
|
13
|
+
vice.buffers.push Vice::Buffer.new(f)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
vice.error 'no such file'
|
17
|
+
end
|
18
|
+
when 'w'
|
19
|
+
if words.length > 1
|
20
|
+
buffer.writef words[1]
|
21
|
+
elsif !buffer.filename.nil?
|
22
|
+
buffer.write
|
23
|
+
else
|
24
|
+
vice.error 'buffer has no filename'
|
25
|
+
end
|
26
|
+
when 'q'
|
27
|
+
vice.buffers.delete buffer
|
28
|
+
vice.prev_buffer
|
29
|
+
exit if vice.buffers.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/vice/version.rb
ADDED
data/test/vice.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require_relative '../lib/vice'
|
4
|
+
|
5
|
+
class TestVice < MiniTest::Test
|
6
|
+
def setup
|
7
|
+
@vice = Vice::Vice.new nil
|
8
|
+
@vice.buffers.push Vice::Buffer.new nil
|
9
|
+
@vice.buffers.push Vice::Buffer.new nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_switch_tab_forward
|
13
|
+
assert_equal 0, @vice.current_buffer
|
14
|
+
|
15
|
+
@vice.next_buffer
|
16
|
+
assert_equal 1, @vice.current_buffer
|
17
|
+
|
18
|
+
@vice.next_buffer
|
19
|
+
assert_equal 2, @vice.current_buffer
|
20
|
+
|
21
|
+
@vice.next_buffer
|
22
|
+
assert_equal 0, @vice.current_buffer
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_switch_tab_backward
|
26
|
+
assert_equal 0, @vice.current_buffer
|
27
|
+
|
28
|
+
@vice.prev_buffer
|
29
|
+
assert_equal 2, @vice.current_buffer
|
30
|
+
|
31
|
+
@vice.prev_buffer
|
32
|
+
assert_equal 1, @vice.current_buffer
|
33
|
+
|
34
|
+
@vice.prev_buffer
|
35
|
+
assert_equal 0, @vice.current_buffer
|
36
|
+
end
|
37
|
+
end
|
data/test/vice/buffer.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require_relative '../../lib/vice'
|
4
|
+
|
5
|
+
class TestBuffer < MiniTest::Test
|
6
|
+
def setup
|
7
|
+
@buffer = Vice::Buffer.new nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_create_buffer
|
11
|
+
assert_equal [''], @buffer.buffer
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_newline_valid
|
15
|
+
@buffer.newline 0
|
16
|
+
assert_equal ['', ''], @buffer.buffer
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_newline_negative_index
|
20
|
+
assert_raises RuntimeError do
|
21
|
+
@buffer.newline -1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_insert_and_insertf_valid
|
26
|
+
@buffer.insert 'chunky bacon'
|
27
|
+
assert_equal 'chunky bacon', @buffer.buffer[0]
|
28
|
+
|
29
|
+
@buffer.insertf 0, 6, ' crispy'
|
30
|
+
assert_equal 'chunky crispy bacon', @buffer.buffer[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_insertf_out_of_bounds
|
34
|
+
# negative line index
|
35
|
+
assert_raises RuntimeError do
|
36
|
+
@buffer.insertf -1, 0, ''
|
37
|
+
end
|
38
|
+
|
39
|
+
# out of bounds line index
|
40
|
+
assert_raises RuntimeError do
|
41
|
+
@buffer.insertf 2, 0, ''
|
42
|
+
end
|
43
|
+
|
44
|
+
@buffer.newline 0
|
45
|
+
|
46
|
+
# negative column index
|
47
|
+
assert_raises RuntimeError do
|
48
|
+
@buffer.insertf 0, -1, ''
|
49
|
+
end
|
50
|
+
|
51
|
+
# out of bounds column index
|
52
|
+
assert_raises RuntimeError do
|
53
|
+
@buffer.insertf 0, 2, ''
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_setline_valid
|
58
|
+
assert_equal [''], @buffer.buffer
|
59
|
+
|
60
|
+
@buffer.setline 0, 'chunky bacon'
|
61
|
+
assert_equal 'chunky bacon', @buffer.buffer[0]
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_rmlinef_valid
|
65
|
+
assert_equal [''], @buffer.buffer
|
66
|
+
|
67
|
+
@buffer.rmlinef 0
|
68
|
+
assert_equal [], @buffer.buffer
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_getline_valid
|
72
|
+
@buffer.setline 0, 'chunky bacon'
|
73
|
+
assert_equal 'chunky bacon', @buffer.getline(0)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_modified
|
77
|
+
assert_equal false, @buffer.modified
|
78
|
+
|
79
|
+
@buffer.newline 0
|
80
|
+
assert_equal true, @buffer.modified
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require_relative '../../lib/vice'
|
4
|
+
|
5
|
+
class TestMovement < MiniTest::Test
|
6
|
+
def test_w_end_of_string
|
7
|
+
assert_equal Vice::Movement.w('abc', 0), 2
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_w_some_words
|
11
|
+
assert_equal Vice::Movement.w('jantje zag eens', 2), 7
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_w_with_other_whitespace_chars
|
15
|
+
assert_equal Vice::Movement.w("jantje\t\nzag eens", 2), 8
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_b_start_of_string
|
19
|
+
assert_equal Vice::Movement.b('aaaaa', 5), 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_b_some_words
|
23
|
+
assert_equal Vice::Movement.b('ab cd', 4), 3
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_b_from_start_of_word
|
27
|
+
assert_equal Vice::Movement.b('jantje zag eens', 11), 7
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_w_w_large_difference
|
31
|
+
string = 'aaa-bbb ccc'
|
32
|
+
assert_equal Vice::Movement.w(string, 1), 4
|
33
|
+
assert_equal Vice::Movement.w_large(string, 1), 8
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_b_b_large_difference
|
37
|
+
string = 'bbb-ccc'
|
38
|
+
assert_equal Vice::Movement.b(string, 5), 4
|
39
|
+
assert_equal Vice::Movement.b_large(string, 5), 0
|
40
|
+
end
|
41
|
+
end
|
data/test/vice/parser.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require_relative '../../lib/vice'
|
4
|
+
|
5
|
+
class TestParser < MiniTest::Test
|
6
|
+
def setup
|
7
|
+
@vice = Vice::Vice.new nil
|
8
|
+
@vice.buffers.push Vice::Buffer.new nil
|
9
|
+
@parser = Vice::Parser.new
|
10
|
+
@buffer = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_parsechar_command_movement_up_down
|
14
|
+
@vice.buffers[@buffer].newline 0
|
15
|
+
assert_equal @vice.buffers[@buffer].cursor.line, 0
|
16
|
+
|
17
|
+
# go down one line
|
18
|
+
@parser.parsechar_command @vice, @buffer, 'j'
|
19
|
+
assert_equal @vice.buffers[@buffer].cursor.line, 1
|
20
|
+
|
21
|
+
# don't go down another line if no more lines exist
|
22
|
+
@parser.parsechar_command @vice, @buffer, 'j'
|
23
|
+
assert_equal @vice.buffers[@buffer].cursor.line, 1
|
24
|
+
|
25
|
+
# go up one line
|
26
|
+
@parser.parsechar_command @vice, @buffer, 'k'
|
27
|
+
assert_equal @vice.buffers[@buffer].cursor.line, 0
|
28
|
+
|
29
|
+
# don't go up another line if no more lines exist
|
30
|
+
@parser.parsechar_command @vice, @buffer, 'k'
|
31
|
+
assert_equal @vice.buffers[@buffer].cursor.line, 0
|
32
|
+
end
|
33
|
+
|
34
|
+
# test that we enter insert mode after pressing i
|
35
|
+
def test_parsechar_command_i
|
36
|
+
assert_equal @vice.mode, :command
|
37
|
+
|
38
|
+
@parser.parsechar_command @vice, @buffer, 'i'
|
39
|
+
assert_equal @vice.mode, :insert
|
40
|
+
end
|
41
|
+
|
42
|
+
# test the I command
|
43
|
+
def test_parsechar_command_i_large
|
44
|
+
@vice.buffers[@buffer].setline 0, 'abcdefg'
|
45
|
+
@vice.buffers[@buffer].cursor.col = 4
|
46
|
+
|
47
|
+
@parser.parsechar_command @vice, @buffer, 'I'
|
48
|
+
assert_equal @vice.mode, :insert
|
49
|
+
assert_equal @vice.buffers[@buffer].cursor.col, 0
|
50
|
+
end
|
51
|
+
|
52
|
+
# test inserting characters
|
53
|
+
def test_insert_some_text
|
54
|
+
@vice.mode = :insert
|
55
|
+
@parser.parsechar_insert @vice, @buffer, 'h'
|
56
|
+
@parser.parsechar_insert @vice, @buffer, 'e'
|
57
|
+
@parser.parsechar_insert @vice, @buffer, 'y'
|
58
|
+
assert_equal @vice.buffers[@buffer].getline(0), 'hey'
|
59
|
+
end
|
60
|
+
|
61
|
+
# test inserting characters in a more random place
|
62
|
+
def test_parsechar_insert_in_the_middle
|
63
|
+
@vice.mode = :insert
|
64
|
+
@vice.buffers[@buffer].newline 0
|
65
|
+
@vice.buffers[@buffer].newline 0
|
66
|
+
@vice.buffers[@buffer].setline 1, 'é melhorser alegre'
|
67
|
+
assert_equal @vice.buffers[@buffer].buffer, ['', 'é melhorser alegre', '']
|
68
|
+
|
69
|
+
@vice.buffers[@buffer].cursor.line = 1
|
70
|
+
@vice.buffers[@buffer].cursor.col = 8
|
71
|
+
@parser.parsechar_insert @vice, @buffer, ' '
|
72
|
+
assert_equal @vice.buffers[@buffer].getline(1), 'é melhor ser alegre'
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_parsechar_insert_escape
|
76
|
+
@vice.mode = :insert
|
77
|
+
|
78
|
+
@parser.parsechar_insert @vice, @buffer, 27
|
79
|
+
assert_equal @vice.mode, :command
|
80
|
+
end
|
81
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vice-editor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- knarka
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: curses
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email: knarka@users.noreply.github.com
|
43
|
+
executables:
|
44
|
+
- vice
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- bin/vice
|
51
|
+
- lib/vice.rb
|
52
|
+
- lib/vice/blitter.rb
|
53
|
+
- lib/vice/buffer.rb
|
54
|
+
- lib/vice/cursor.rb
|
55
|
+
- lib/vice/movement.rb
|
56
|
+
- lib/vice/parser.rb
|
57
|
+
- lib/vice/prompt.rb
|
58
|
+
- lib/vice/version.rb
|
59
|
+
- test/vice.rb
|
60
|
+
- test/vice/buffer.rb
|
61
|
+
- test/vice/movement.rb
|
62
|
+
- test/vice/parser.rb
|
63
|
+
homepage: https://github.com/knarka/vice
|
64
|
+
licenses: []
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 2.3.3
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.5.2.1
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: vi-like text editor
|
86
|
+
test_files: []
|