Skip to content

Commit e9f241a

Browse files
authored
micro: Handle +/regex search from args (zyedidia#3767)
This is a feature found in vim and commonly used by Linux kernel test robots to give context about warnings and/or failures. e.g. vim +/imem_size +623 drivers/net/ipa/ipa_mem.c The order in which the commands appear in the args determines in which order the "goto line:column" and search will be executed.
1 parent bbea2a3 commit e9f241a

File tree

3 files changed

+89
-30
lines changed

3 files changed

+89
-30
lines changed

assets/packaging/micro.1

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
.TH micro 1 "2025-08-16"
1+
.TH micro 1 "2025-09-03"
22
.SH NAME
33
micro \- A modern and intuitive terminal-based text editor
44
.SH SYNOPSIS
55
.B micro
66
.RI [ OPTION ]...\&
77
.RI [ FILE ]...\&
8-
.RI [+ LINE [: COL ]]
8+
.RI [+ LINE [: COL ]]\&
9+
.RI [+/ REGEX ]
910
.br
1011
.B micro
1112
.RI [ OPTION ]...\&
@@ -40,6 +41,11 @@ Specify a custom location for the configuration directory
4041
Specify a line and column to start the cursor at when opening a buffer
4142
.RE
4243
.PP
44+
.RI +/ REGEX
45+
.RS 4
46+
Specify a regex to search for when opening a buffer
47+
.RE
48+
.PP
4349
.B \-options
4450
.RS 4
4551
Show all options help and exit

cmd/micro/micro.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var (
4848
func InitFlags() {
4949
// Note: keep this in sync with the man page in assets/packaging/micro.1
5050
flag.Usage = func() {
51-
fmt.Println("Usage: micro [OPTION]... [FILE]... [+LINE[:COL]]")
51+
fmt.Println("Usage: micro [OPTION]... [FILE]... [+LINE[:COL]] [+/REGEX]")
5252
fmt.Println(" micro [OPTION]... [FILE[:LINE[:COL]]]... (only if the `parsecursor` option is enabled)")
5353
fmt.Println("-clean")
5454
fmt.Println(" \tClean the configuration directory and exit")
@@ -57,6 +57,8 @@ func InitFlags() {
5757
fmt.Println("FILE:LINE[:COL] (only if the `parsecursor` option is enabled)")
5858
fmt.Println("FILE +LINE[:COL]")
5959
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
60+
fmt.Println("+/REGEX")
61+
fmt.Println(" \tSpecify a regex to search for when opening a buffer")
6062
fmt.Println("-options")
6163
fmt.Println(" \tShow all options help and exit")
6264
fmt.Println("-debug")
@@ -167,39 +169,60 @@ func LoadInput(args []string) []*buffer.Buffer {
167169
}
168170

169171
files := make([]string, 0, len(args))
172+
170173
flagStartPos := buffer.Loc{-1, -1}
171-
flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
172-
for _, a := range args {
173-
match := flagr.FindStringSubmatch(a)
174-
if len(match) == 3 && match[2] != "" {
175-
line, err := strconv.Atoi(match[1])
174+
posFlagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
175+
posIndex := -1
176+
177+
searchText := ""
178+
searchFlagr := regexp.MustCompile(`^\+\/(.+)$`)
179+
searchIndex := -1
180+
181+
for i, a := range args {
182+
posMatch := posFlagr.FindStringSubmatch(a)
183+
if len(posMatch) == 3 && posMatch[2] != "" {
184+
line, err := strconv.Atoi(posMatch[1])
176185
if err != nil {
177186
screen.TermMessage(err)
178187
continue
179188
}
180-
col, err := strconv.Atoi(match[2])
189+
col, err := strconv.Atoi(posMatch[2])
181190
if err != nil {
182191
screen.TermMessage(err)
183192
continue
184193
}
185194
flagStartPos = buffer.Loc{col - 1, line - 1}
186-
} else if len(match) == 3 && match[2] == "" {
187-
line, err := strconv.Atoi(match[1])
195+
posIndex = i
196+
} else if len(posMatch) == 3 && posMatch[2] == "" {
197+
line, err := strconv.Atoi(posMatch[1])
188198
if err != nil {
189199
screen.TermMessage(err)
190200
continue
191201
}
192202
flagStartPos = buffer.Loc{0, line - 1}
203+
posIndex = i
193204
} else {
194-
files = append(files, a)
205+
searchMatch := searchFlagr.FindStringSubmatch(a)
206+
if len(searchMatch) == 2 {
207+
searchText = searchMatch[1]
208+
searchIndex = i
209+
} else {
210+
files = append(files, a)
211+
}
195212
}
196213
}
197214

215+
command := buffer.Command{
216+
StartCursor: flagStartPos,
217+
SearchRegex: searchText,
218+
SearchAfterStart: searchIndex > posIndex,
219+
}
220+
198221
if len(files) > 0 {
199222
// Option 1
200223
// We go through each file and load it
201224
for i := 0; i < len(files); i++ {
202-
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
225+
buf, err := buffer.NewBufferFromFileWithCommand(files[i], btype, command)
203226
if err != nil {
204227
screen.TermMessage(err)
205228
continue
@@ -216,10 +239,10 @@ func LoadInput(args []string) []*buffer.Buffer {
216239
screen.TermMessage("Error reading from stdin: ", err)
217240
input = []byte{}
218241
}
219-
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
242+
buffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), filename, btype, command))
220243
} else {
221244
// Option 3, just open an empty buffer
222-
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
245+
buffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), filename, btype, command))
223246
}
224247

225248
return buffers

internal/buffer/buffer.go

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ const (
215215

216216
type DiffStatus byte
217217

218+
type Command struct {
219+
StartCursor Loc
220+
SearchRegex string
221+
SearchAfterStart bool
222+
}
223+
224+
var emptyCommand = Command{
225+
StartCursor: Loc{-1, -1},
226+
SearchRegex: "",
227+
SearchAfterStart: false,
228+
}
229+
218230
// Buffer stores the main information about a currently open file including
219231
// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
220232
// all the cursors, the syntax highlighting info, the settings for the buffer
@@ -256,19 +268,19 @@ type Buffer struct {
256268
OverwriteMode bool
257269
}
258270

259-
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
260-
// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location
271+
// NewBufferFromFileWithCommand opens a new buffer with a given command
272+
// If cmd.StartCursor is {-1, -1} the location does not overwrite what the cursor location
261273
// would otherwise be (start of file, or saved cursor position if `savecursor` is
262274
// enabled)
263-
func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) {
275+
func NewBufferFromFileWithCommand(path string, btype BufType, cmd Command) (*Buffer, error) {
264276
var err error
265277
filename := path
266-
if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 {
278+
if config.GetGlobalOption("parsecursor").(bool) && cmd.StartCursor.X == -1 && cmd.StartCursor.Y == -1 {
267279
var cursorPos []string
268280
filename, cursorPos = util.GetPathAndCursorPosition(filename)
269-
cursorLoc, err = ParseCursorLocation(cursorPos)
281+
cmd.StartCursor, err = ParseCursorLocation(cursorPos)
270282
if err != nil {
271-
cursorLoc = Loc{-1, -1}
283+
cmd.StartCursor = Loc{-1, -1}
272284
}
273285
}
274286

@@ -304,7 +316,7 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
304316
} else if err != nil {
305317
return nil, err
306318
} else {
307-
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
319+
buf = NewBuffer(file, util.FSize(file), filename, btype, cmd)
308320
if buf == nil {
309321
return nil, errors.New("could not open file")
310322
}
@@ -323,25 +335,26 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
323335
// It will return an empty buffer if the path does not exist
324336
// and an error if the file is a directory
325337
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
326-
return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1})
338+
return NewBufferFromFileWithCommand(path, btype, emptyCommand)
327339
}
328340

329-
// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc
330-
func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer {
331-
return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype)
341+
// NewBufferFromStringWithCommand creates a new buffer containing the given string
342+
// with a cursor loc and a search text
343+
func NewBufferFromStringWithCommand(text, path string, btype BufType, cmd Command) *Buffer {
344+
return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, cmd)
332345
}
333346

334347
// NewBufferFromString creates a new buffer containing the given string
335348
func NewBufferFromString(text, path string, btype BufType) *Buffer {
336-
return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
349+
return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, emptyCommand)
337350
}
338351

339352
// NewBuffer creates a new buffer from a given reader with a given path
340353
// Ensure that ReadSettings and InitGlobalSettings have been called before creating
341354
// a new buffer
342355
// Places the cursor at startcursor. If startcursor is -1, -1 places the
343356
// cursor at an autodetected location (based on savecursor or :LINE:COL)
344-
func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer {
357+
func NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) *Buffer {
345358
absPath, err := filepath.Abs(path)
346359
if err != nil {
347360
absPath = path
@@ -436,8 +449,8 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
436449
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
437450
}
438451

439-
if startcursor.X != -1 && startcursor.Y != -1 {
440-
b.StartCursor = startcursor
452+
if cmd.StartCursor.X != -1 && cmd.StartCursor.Y != -1 {
453+
b.StartCursor = cmd.StartCursor
441454
} else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
442455
err := b.Unserialize()
443456
if err != nil {
@@ -448,6 +461,23 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
448461
b.AddCursor(NewCursor(b, b.StartCursor))
449462
b.GetActiveCursor().Relocate()
450463

464+
if cmd.SearchRegex != "" {
465+
match, found, _ := b.FindNext(cmd.SearchRegex, b.Start(), b.End(), b.StartCursor, true, true)
466+
if found {
467+
if cmd.SearchAfterStart {
468+
// Search from current cursor and move it accordingly
469+
b.GetActiveCursor().SetSelectionStart(match[0])
470+
b.GetActiveCursor().SetSelectionEnd(match[1])
471+
b.GetActiveCursor().OrigSelection[0] = b.GetActiveCursor().CurSelection[0]
472+
b.GetActiveCursor().OrigSelection[1] = b.GetActiveCursor().CurSelection[1]
473+
b.GetActiveCursor().GotoLoc(match[1])
474+
}
475+
b.LastSearch = cmd.SearchRegex
476+
b.LastSearchRegex = true
477+
b.HighlightSearch = b.Settings["hlsearch"].(bool)
478+
}
479+
}
480+
451481
if !b.Settings["fastdirty"].(bool) && !found {
452482
if size > LargeFileThreshold {
453483
// If the file is larger than LargeFileThreshold fastdirty needs to be on

0 commit comments

Comments
 (0)