-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathvi_motions.lua
402 lines (356 loc) · 11.2 KB
/
vi_motions.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
local M = {}
local vi_ta_util = require 'textadept-vi.vi_ta_util'
local line_length = vi_ta_util.line_length
local buf_state = vi_ta_util.buf_state
-- Normal motions
function M.char_left()
local line, pos = buffer.get_cur_line()
if pos > 1 then buffer.char_left() end
end
function M.char_right()
local line, pos = buffer.get_cur_line()
local docpos = buffer.current_pos
-- Don't include line ending characters, so we can't use buffer.line_length().
local lineno = buffer:line_from_position(docpos)
local length = line_length(lineno)
if pos < length then
buffer.char_right()
end
end
--- Move the cursor down one line.
--
function M.line_down()
local bufstate = buf_state(buffer)
local lineno = buffer.line_from_position(buffer.current_pos)
local linestart = buffer.position_from_line(lineno)
if lineno < buffer.line_count then
local ln = lineno + 1
-- Find a visible line (skip over folds)
while not buffer.line_visible[ln] do
ln = ln + 1
if ln >= buffer.line_count then
return
end
end
local col = bufstate.col or (buffer.current_pos - linestart)
bufstate.col = col -- Try to stay in the same column
if col >= line_length(ln) then
col = line_length(ln) - 1
end
if col < 0 then col = 0 end
buffer:goto_pos(buffer.position_from_line(ln) + col)
end
end
--- Move the cursor up one line.
--
function M.line_up()
local bufstate = buf_state(buffer)
local lineno = buffer.line_from_position(buffer.current_pos)
local linestart = buffer.position_from_line(lineno)
if lineno >= 1 then
local ln = lineno - 1
-- Find a visible line (skip over folds)
while not buffer.line_visible[ln] do
ln = ln - 1
if ln < 0 then
return
end
end
local col = bufstate.col or buffer.current_pos - linestart
bufstate.col = col
if col >= line_length(ln) then
col = line_length(ln) - 1
end
if col < 0 then col = 0 end
buffer:goto_pos(buffer.position_from_line(ln) + col)
end
end
-- Move to the start of the next word
function M.word_right()
buffer.word_right()
local lineno = buffer.line_from_position(buffer.current_pos)
local col = buffer.current_pos - buffer.position_from_line(lineno)
-- Textadept sticks at the end of the line.
if col >= line_length(lineno) then
if lineno == buffer.line_count then
buffer:char_left()
else
buffer:word_right()
end
end
end
-- Move to the start of the previous word
function M.word_left()
buffer.word_left()
local lineno = buffer.line_from_position(buffer.current_pos)
local col = buffer.current_pos - buffer.position_from_line(lineno)
-- Textadept sticks at the end of the line.
if col >= line_length(lineno) then
buffer:word_left()
end
end
-- Move to the end of the next word
function M.word_end()
buffer.char_right()
buffer.word_right_end()
local lineno = buffer:line_from_position(buffer.current_pos)
local col = buffer.current_pos - buffer.position_from_line(lineno)
if col == 0 then
-- word_right_end sticks at start of
-- line.
buffer:word_right_end()
end
buffer.char_left()
end
-- Go to column 0
function M.line_start(rep)
buffer:home()
end
-- Go to first non-blank on line, or if there to first column.
function M.line_beg(rep)
buffer.home() -- Go to beginning of line
buffer.vc_home() -- swaps between beginning/first visible
end
-- go (rep-1) visible lines down (ie. jump over folds), then go
-- to the first non-blank character of the current line
function M.line_down_then_line_beg(rep)
for _=2,rep do M.line_down() end -- go rep-1 lines down
M.line_beg()
end
-- Move paragraphs at a time
local function paragraph_move(forward, rep)
local step = forward and 1 or -1
local endAt = forward and buffer.line_count or 0
for i=1,rep do
-- Go until blank line on an open fold
for lineno=buffer:line_from_position(buffer.current_pos)+step,endAt,step do
if lineno == endAt or (buffer.line_visible[lineno] and
line_length(lineno) == 0) then
buffer:goto_line(lineno)
break
end
end
end
end
function M.paragraph_backward(rep)
paragraph_move(false, rep)
end
function M.paragraph_forward(rep)
paragraph_move(true, rep)
end
-- Move to the end of the line
function M.line_end(rep)
if rep and rep > 1 then
for i=1,rep-1 do
buffer:line_down()
end
end
buffer:line_end()
local line, pos = buffer:get_cur_line()
if pos > 1 then buffer:char_left() end
end
-- Select motions (return start,end_)
function M.sel_line(numlines)
if not numlines or numlines < 1 then numlines = 1 end
local lineno = buffer:line_from_position(buffer.current_pos)
return buffer.current_pos, buffer.line_end_position[lineno + numlines - 1]
end
-- Helpers for classifying characters for words:
-- A word is a sequence of (_,digits,letters) or (other non-blanks).
local function w_isblank(c)
return c:match("%s")
end
local function w_isword(c)
return c:match("[%a%d_]")
end
local function w_isother(c)
return c:match("[^%s%a%d_]")
end
function M.sel_word(numwords)
if not numwords or numwords < 1 then numwords = 1 end
local buflen = buffer.length
local pos = buffer.current_pos
-- Simple case: off the end of the buffer.
if pos > buflen then
return buflen, buflen
end
-- Now find the current character.
local nextpos = buffer:position_after(pos)
local c = buffer:text_range(pos, nextpos)
if c:match("%s") then
-- If on whitespace, behave just like 'w'.
for i=1,numwords do
M.word_right()
return pos, buffer.current_pos
end
else
-- Otherwise, behave as "change word".
local p = pos
for i=1,numwords do
-- Skip over whitespace
while p <= buflen and w_isblank(c) do
p = nextpos
nextpos = buffer:position_after(nextpos)
c = buffer:text_range(p, nextpos)
end
-- And now find the end of the word or nonblank-sequence
if p <= buflen and w_isword(c) then
while p <= buflen and w_isword(c) do
p = nextpos
nextpos = buffer:position_after(nextpos)
c = buffer:text_range(p, nextpos)
end
else
while p <= buflen and w_isother(c) do
p = nextpos
nextpos = buffer:position_after(nextpos)
c = buffer:text_range(p, nextpos)
end
end
end
return pos, p
end
end
function M.goto_line(lineno)
if lineno > 0 then
-- Textadept does zero-based line numbers.
buffer:goto_line(lineno)
else
-- With no arg, go to last line.
buffer:document_end()
buffer:home()
end
end
-- Same a goto_line but with a different default.
function M.goto_line_0(lineno)
if lineno > 0 then
-- Textadept does zero-based line numbers.
buffer:goto_line(lineno)
else
-- With no arg, or 0, go to last line.
buffer:goto_line(1)
buffer:home()
end
end
function M.match_brace()
local orig_pos = buffer.current_pos
-- Simple case: match on current character
local pos = buffer:brace_match(buffer.current_pos, 0)
if pos >= 0 then
buffer:goto_pos(pos)
return
end
-- Get the current line and position, as we'll need it for
-- the next tests.
local line, idx = buffer:get_cur_line()
-- Are we on a C conditional?
do
-- TODO: cache the pattern
local P = lpeg.P
local S = lpeg.S
local C = lpeg.C
local R = lpeg.R
local ws = S' \t'
local cpppat = (ws ^ 0) * P"#" * (ws ^ 0) *
(C(P"if" + P"elif" + P"else"+ P"ifdef" + P"endif") + -R("az", "AZ", "09"))
local cppcond = cpppat:match(line)
-- How each operation adjusts the nesting level
local nestop = {
['if'] = 1,
['ifdef'] = 1,
['else'] = 0,
['elif'] = 0,
['endif'] = -1,
}
local lineno = buffer.line_from_position(buffer.current_pos)
local level = 0
if cppcond == 'endif' then
-- Search backwards for the original if/ifdef
while lineno > 0 do
lineno = lineno - 1
line = buffer:get_line(lineno)
cppcond = cpppat:match(line)
if nestop[cppcond] == 1 and level == 0 then
-- found!
buffer.goto_line(lineno)
return
elseif cppcond then
level = level + nestop[cppcond]
end
end
elseif cppcond then
-- Search forwards for matching level
local lines = buffer.line_count
while lineno < lines do
lineno = lineno + 1
line = buffer:get_line(lineno)
cppcond = cpppat:match(line)
if cppcond and level == 0 and nestop[cppcond] < 1 then
-- found!
buffer.goto_line(lineno)
return
elseif cppcond then
level = level + nestop[cppcond]
end
end
end
end
-- Try searching forwards on the line
local bracketpat = "[%(%)<>%[%]{}]"
local newidx, _, c = line:find(bracketpat, idx+1)
if newidx then
pos = buffer:brace_match(orig_pos + newidx - idx - 1, 0)
if pos >= 0 then
buffer:goto_pos(pos)
end
return
end
end
--- Redo the previous search
function M.search_next()
vi_mode.search_mode.restart()
end
-- Redo the previous search in the opposite direction.
function M.search_prev()
vi_mode.search_mode.restart_rev()
end
--- Search forward for the word under the cursor
function M.search_word_next()
vi_mode.search_mode.search_word()
end
--- Search backwards for the word under the cursor
function M.search_word_prev()
vi_mode.search_mode.search_word_rev()
end
--- Begin search mode (entering a pattern).
-- cb will be called later with or a movement description.
function M.search_fwd(cb)
vi_mode.search_mode.start(function(movf)
cb({ 'exclusive', movf, 1 })
end)
end
--- Begin search mode backwards (entering a pattern).
-- cb will be called later with or a movement description.
function M.search_back(cb)
vi_mode.search_mode.start_rev(function(movf)
cb({ 'exclusive', movf, 1 })
end)
end
-- Return the position (if any) of the next occurance of character c
-- on the current line.
function M.next_char_of(c)
local orig_pos = buffer.current_pos
local lineno = buffer:line_from_position(buffer.current_pos)
buffer.target_start = buffer.current_pos + 1
buffer.target_end = buffer.line_end_position[lineno]
local orig_flags = buffer.search_flags
buffer.search_flags = buffer.FIND_MATCHCASE
local pos = buffer:search_in_target(c)
buffer.search_flags = orig_flags
if pos >= 0 then
buffer:goto_pos(pos)
else
buffer:goto_pos(orig_pos)
end
end
return M