Skip to content

Commit 1134bae

Browse files
committed
Add an is_line_max function to clarify right ward motions
- Change `s` to be a synonym, rather than its own function - swap ordering of `at_junction_type` params so that the last arg can be more flexible (different number of args)
1 parent c3f67a9 commit 1134bae

File tree

7 files changed

+77
-26
lines changed

7 files changed

+77
-26
lines changed

src/buffer.jl

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module Buffer
22
import REPL.LineEdit as LE
33
export VimBuffer, mode, VimMode, normal_mode, insert_mode, testbuf, readall, freeze,
4-
BufferRecord, chars, peek_left, peek_right, read_left, read_right
4+
BufferRecord, chars, peek_left, peek_right, peek_two_right, read_left, read_right
55

66
@enum VimMode begin
77
normal_mode
@@ -125,6 +125,19 @@ function peek_right(buf::IO)::Union{Char,Nothing}
125125
return nothing
126126
end
127127

128+
"""
129+
Read up to 2 valid UTF-8 character right of the current position and leave the buffer in the same position.
130+
131+
Returns a tuple with each successful character (or nothing for a character not read successfully)
132+
"""
133+
function peek_two_right(buf::IO)
134+
origin = position(buf)
135+
c1 = read_right(buf)
136+
c2 = read_right(buf)
137+
seek(buf, origin)
138+
return (c1, c2)
139+
end
140+
128141
"""
129142
Read 1 valid UTF-8 character right of the current position.
130143

src/execute.jl

+10-7
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,13 @@ function execute(buf, command::MotionCommand)::Union{VimMode,ReplAction,Nothing}
3737
nothing
3838
end
3939
else
40-
# execute the motion object
41-
motion(buf)
40+
k = key(command)
41+
if !(k == 'l' && is_line_max(buf))
42+
# we shouldn't move past the last character of the line
43+
44+
# execute the motion object
45+
motion(buf)
46+
end
4247
end
4348
end
4449
return repl_action
@@ -89,9 +94,6 @@ const insert_functions = Dict{Char,Function}(
8994
motion
9095
end
9196
end,
92-
's' => buf -> begin
93-
delete(buf, right(buf))
94-
end
9597
# _ => gen_motion(buf, command)
9698
)
9799

@@ -119,7 +121,7 @@ function execute(buf, command::OperatorCommand)::Union{VimMode,Nothing}
119121
# see vim help :h cw regarding this exception
120122
if command.operator == 'c' && command.action.name in ['w', 'W']
121123
# in the middle of a word
122-
if at_junction_type(buf, In{>:Word}) || at_junction_type(buf, Start{>:Word})
124+
if at_junction_type(In{>:Word}, buf) || at_junction_type(Start{>:Word}, buf)
123125
new_name = if command.action.name == 'w'
124126
'e'
125127
else
@@ -172,7 +174,8 @@ function execute(buf, command::SynonymCommand)::Union{VimMode,Nothing}
172174
'X' => "dh",
173175
'C' => "c\$",
174176
'D' => "d\$",
175-
'S' => "cc"
177+
'S' => "cc",
178+
's' => "cl",
176179
)
177180
new_command = parse_command("$(command.r1)$(synonyms[command.operator])")
178181

src/motion.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ end
113113

114114
function left(buf::IO)::Motion
115115
start = position(buf)
116-
@loop_guard while position(buf) > 0
116+
@loop_guard while position(buf) > 0 && !is_line_start(buf)
117117
seek(buf, position(buf) - 1)
118118
c = peek(buf)
119119
(((c & 0x80) == 0) || ((c & 0xc0) == 0xc0)) && break

src/parse.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ end
5353
const UNDO_REDO = "(?|(u)|(\x12))"
5454
const TEXTOBJECT = "$REPEAT[ai][wWsp]"
5555
const PARTIALTEXTOBJECT = "$REPEAT[ai]([wWsp])?"
56-
const DELETECHARS = "[xXDCS]"
57-
const INSERTCHARS = "[aAiIoOs]"
56+
const DELETECHARS = "[xXDCSs]"
57+
const INSERTCHARS = "[aAiIoO]"
5858
const OPERATOR = "[ydc]"
5959
const RULES = TupleDict(
6060
"^(?<c>$INSERTCHARS)\$" |> Regex => InsertCommand, # insert commands

src/textutils.jl

+31-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
"""
2+
Getting carried away with types and multiple dispatch: text utility style
3+
"""
14
module TextUtils
25
import Base: *
36
using REPL
@@ -9,33 +12,41 @@ export is_newline, is_whitespace, is_word_char, TextChar, WordChar, WhitespaceCh
912
chars_by_cursor, junction_type, at_junction_type, Text, Object, Word, Line, Whitespace, Junction, Start, End, In,
1013
is_alphanumeric, is_alphabetic, is_uppercase, is_lowercase, is_punctuation, is_line_start, is_line_end,
1114
is_word_end, is_word_start, is_object_start, is_object_end, is_in_object, is_whitespace_end, is_whitespace_start, is_in_word, chars,
12-
peek_left, peek_right, read_left, read_right
15+
peek_left, peek_right, read_left, read_right, is_line_max
1316

1417
"""
1518
Determine whether the buffer is currently at the start of a text object.
1619
Whitespace is not included as a text object.
1720
"""
18-
is_word_start(buf) = at_junction_type(buf, Start{>:Word})
19-
is_whitespace_start(buf) = at_junction_type(buf, Start{>:Whitespace})
21+
is_word_start(buf) = at_junction_type(Start{>:Word}, buf)
22+
is_whitespace_start(buf) = at_junction_type(Start{>:Whitespace}, buf)
2023
"""
2124
Whether the buffer is currently at the start of a non-whitespace
2225
block
2326
"""
24-
is_object_start(buf) = at_junction_type(buf, Start{>:Object})
27+
is_object_start(buf) = at_junction_type(Start{>:Object}, buf)
2528

2629
"""
2730
Whether the buffer is currently at the end of a text object. Whitespace is not included as a text object.
2831
"""
29-
is_word_end(buf) = at_junction_type(buf, End{>:Word})
30-
is_object_end(buf) = at_junction_type(buf, End{>:Object})
31-
is_whitespace_end(buf) = at_junction_type(buf, End{>:Whitespace})
32+
is_word_end(buf) = at_junction_type(End{>:Word}, buf)
33+
is_object_end(buf) = at_junction_type(End{>:Object}, buf)
34+
is_whitespace_end(buf) = at_junction_type(End{>:Whitespace}, buf)
3235

3336
"""
3437
Whether the buffer is at the start of a line, in the literal sense
3538
In other words, is the char before the cursor a newline
3639
"""
37-
is_line_start(buf) = at_junction_type(buf, Start{>:Line})
38-
is_line_end(buf) = at_junction_type(buf, End{>:Line})
40+
is_line_start(buf) = at_junction_type(Start{>:Line}, buf)
41+
is_line_end(buf) = at_junction_type(End{>:Line}, buf)
42+
43+
"""
44+
Whether the buffer's location is the right-most that the cursor is allowed to go; the "end of the line" so that the cursor does not get placed at a newline where there is not actually a character to operate on.
45+
"""
46+
function is_line_max(buf)
47+
c1, c2 = peek_two_right(buf)
48+
return at_junction_type(End{>:Line}, c1, c2)
49+
end
3950

4051

4152
"""
@@ -44,10 +55,10 @@ Whether the buffer is currently in an object of a continuous type (not between t
4455
function is_in_word(buf)
4556
a, b = chars_by_cursor(buf)
4657
typeof(a) == typeof(b) || return false
47-
at_junction_type(buf, In{>:Word})
58+
at_junction_type(In{>:Word}, buf)
4859
end
4960

50-
is_in_object(buf) = at_junction_type(buf, In{<:Object})
61+
is_in_object(buf) = at_junction_type(In{<:Object}, buf)
5162

5263
"""
5364
Get the
@@ -183,8 +194,15 @@ junction_type(char1::T, char2::T) where {T<:SpaceChar} = Set([In{Whitespace}()])
183194
"""
184195
Whether the given buffer is currently at a junction of type junc
185196
"""
186-
function at_junction_type(buf, junc_type)
187-
for junc in junction_type(buf)
197+
function at_junction_type(junc_type, obj::Vararg)
198+
juncs = junction_type(obj...)
199+
# return at_junction_type(junction_type(obj, junc_type...))
200+
return at_junction_type(junc_type, juncs)
201+
end
202+
203+
function at_junction_type(junc_type, juncs::Set)
204+
# TODO nuke this whole thing
205+
for junc in juncs
188206
if junc isa junc_type
189207
@debug "at_junction_type?" junc_type chars_by_cursor(buf) at_junction_type = true
190208
return true

test/command.jl

+6-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ end
4141
@testset "basic movements" begin
4242
@test run("asdf|", "h") == testbuf("asd|f")
4343
@test run("asdf|", "l") == testbuf("asdf|")
44-
@test run("asd|f", "l") == testbuf("asdf|")
44+
@test run("asd|f", "l") == testbuf("asd|f")
45+
@test run("|asdf", "l") == testbuf("a|sdf")
4546
@test run("a|sdf", "\$") == testbuf("asd|f")
4647
@test run("asd|f", "\$") == testbuf("asd|f")
4748

@@ -69,6 +70,9 @@ end
6970
@test run("|asdf", "dh") == testbuf("|asdf")
7071
@test run("asd|f", "X") == testbuf("as|f")
7172
@test run("asd|f", "x") == testbuf("as|d")
73+
74+
@test run("|asdf", "s") == testbuf("|i|sdf")
75+
@test run("asd|f", "s") == testbuf("asd|i|")
7276
end
7377

7478
@testset "de" begin
@@ -152,6 +156,7 @@ end
152156

153157
@testset "unicode" begin
154158
# https://github.com/caleb-allen/VimBindings.jl/issues/95
159+
@test run("fo|o", "x") == testbuf("f|o")
155160
@test run("(0b0000 ⊻ 0b000|0)", "x") == testbuf("(0b0000 ⊻ 0b000|)")
156161
@test run("0b0000 ⊻ 0b000|0", "x") == testbuf("0b0000 ⊻ 0b00|0")
157162

test/textutils.jl

+13-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ end
128128
@test is_in_object(testbuf("hello%%|%%")) == true
129129
@test is_in_object(testbuf("|hello%%%%")) == false
130130
end
131-
@testset "is line start " begin
131+
@testset "start and end of lines" begin
132132
@test is_line_end(testbuf("|\n")) == true
133133
@test is_line_end(testbuf("one line\t|\nhello")) == true
134134
@test is_line_end(testbuf("one line\t\nhello|")) == true
@@ -141,6 +141,18 @@ end
141141
@test is_line_start(testbuf("one line|\nhello")) == false
142142
@test is_line_start(testbuf("one line \n|hello")) == true
143143

144+
@test is_line_max(testbuf("""
145+
|hello world!
146+
""")) == false
147+
148+
@test is_line_max(testbuf("""
149+
hello world|!
150+
""")) == true
151+
152+
@test is_line_max(testbuf("|")) == true
153+
@test is_line_max(testbuf("foo|")) == true
154+
@test is_line_max(testbuf("fo|o")) == true
155+
@test is_line_max(testbuf("f|oo")) == false
144156
end
145157

146158
@testset "test buffer" begin

0 commit comments

Comments
 (0)