diff --git a/assets/packaging/micro.1 b/assets/packaging/micro.1 index dbd2cc80..909bbde6 100644 --- a/assets/packaging/micro.1 +++ b/assets/packaging/micro.1 @@ -1,11 +1,12 @@ -.TH micro 1 "2025-08-16" +.TH micro 1 "2025-09-03" .SH NAME micro \- A modern and intuitive terminal-based text editor .SH SYNOPSIS .B micro .RI [ OPTION ]...\& .RI [ FILE ]...\& -.RI [+ LINE [: COL ]] +.RI [+ LINE [: COL ]]\& +.RI [+/ REGEX ] .br .B micro .RI [ OPTION ]...\& @@ -40,6 +41,11 @@ Specify a custom location for the configuration directory Specify a line and column to start the cursor at when opening a buffer .RE .PP +.RI +/ REGEX +.RS 4 +Specify a regex to search for when opening a buffer +.RE +.PP .B \-options .RS 4 Show all options help and exit diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index a45ab938..cf85df20 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -48,7 +48,7 @@ var ( func InitFlags() { // Note: keep this in sync with the man page in assets/packaging/micro.1 flag.Usage = func() { - fmt.Println("Usage: micro [OPTION]... [FILE]... [+LINE[:COL]]") + fmt.Println("Usage: micro [OPTION]... [FILE]... [+LINE[:COL]] [+/REGEX]") fmt.Println(" micro [OPTION]... [FILE[:LINE[:COL]]]... (only if the `parsecursor` option is enabled)") fmt.Println("-clean") fmt.Println(" \tClean the configuration directory and exit") @@ -57,6 +57,8 @@ func InitFlags() { fmt.Println("FILE:LINE[:COL] (only if the `parsecursor` option is enabled)") fmt.Println("FILE +LINE[:COL]") fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer") + fmt.Println("+/REGEX") + fmt.Println(" \tSpecify a regex to search for when opening a buffer") fmt.Println("-options") fmt.Println(" \tShow all options help and exit") fmt.Println("-debug") @@ -167,39 +169,60 @@ func LoadInput(args []string) []*buffer.Buffer { } files := make([]string, 0, len(args)) + flagStartPos := buffer.Loc{-1, -1} - flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`) - for _, a := range args { - match := flagr.FindStringSubmatch(a) - if len(match) == 3 && match[2] != "" { - line, err := strconv.Atoi(match[1]) + posFlagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`) + posIndex := -1 + + searchText := "" + searchFlagr := regexp.MustCompile(`^\+\/(.+)$`) + searchIndex := -1 + + for i, a := range args { + posMatch := posFlagr.FindStringSubmatch(a) + if len(posMatch) == 3 && posMatch[2] != "" { + line, err := strconv.Atoi(posMatch[1]) if err != nil { screen.TermMessage(err) continue } - col, err := strconv.Atoi(match[2]) + col, err := strconv.Atoi(posMatch[2]) if err != nil { screen.TermMessage(err) continue } flagStartPos = buffer.Loc{col - 1, line - 1} - } else if len(match) == 3 && match[2] == "" { - line, err := strconv.Atoi(match[1]) + posIndex = i + } else if len(posMatch) == 3 && posMatch[2] == "" { + line, err := strconv.Atoi(posMatch[1]) if err != nil { screen.TermMessage(err) continue } flagStartPos = buffer.Loc{0, line - 1} + posIndex = i } else { - files = append(files, a) + searchMatch := searchFlagr.FindStringSubmatch(a) + if len(searchMatch) == 2 { + searchText = searchMatch[1] + searchIndex = i + } else { + files = append(files, a) + } } } + command := buffer.Command{ + StartCursor: flagStartPos, + SearchRegex: searchText, + SearchAfterStart: searchIndex > posIndex, + } + if len(files) > 0 { // Option 1 // We go through each file and load it for i := 0; i < len(files); i++ { - buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos) + buf, err := buffer.NewBufferFromFileWithCommand(files[i], btype, command) if err != nil { screen.TermMessage(err) continue @@ -216,10 +239,10 @@ func LoadInput(args []string) []*buffer.Buffer { screen.TermMessage("Error reading from stdin: ", err) input = []byte{} } - buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos)) + buffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), filename, btype, command)) } else { // Option 3, just open an empty buffer - buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos)) + buffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), filename, btype, command)) } return buffers diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 998979b9..ce36988b 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -215,6 +215,18 @@ const ( type DiffStatus byte +type Command struct { + StartCursor Loc + SearchRegex string + SearchAfterStart bool +} + +var emptyCommand = Command{ + StartCursor: Loc{-1, -1}, + SearchRegex: "", + SearchAfterStart: false, +} + // Buffer stores the main information about a currently open file including // the actual text (in a LineArray), the undo/redo stack (in an EventHandler) // all the cursors, the syntax highlighting info, the settings for the buffer @@ -256,19 +268,19 @@ type Buffer struct { OverwriteMode bool } -// NewBufferFromFileAtLoc opens a new buffer with a given cursor location -// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location +// NewBufferFromFileWithCommand opens a new buffer with a given command +// If cmd.StartCursor is {-1, -1} the location does not overwrite what the cursor location // would otherwise be (start of file, or saved cursor position if `savecursor` is // enabled) -func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) { +func NewBufferFromFileWithCommand(path string, btype BufType, cmd Command) (*Buffer, error) { var err error filename := path - if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 { + if config.GetGlobalOption("parsecursor").(bool) && cmd.StartCursor.X == -1 && cmd.StartCursor.Y == -1 { var cursorPos []string filename, cursorPos = util.GetPathAndCursorPosition(filename) - cursorLoc, err = ParseCursorLocation(cursorPos) + cmd.StartCursor, err = ParseCursorLocation(cursorPos) if err != nil { - cursorLoc = Loc{-1, -1} + cmd.StartCursor = Loc{-1, -1} } } @@ -304,7 +316,7 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, } else if err != nil { return nil, err } else { - buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype) + buf = NewBuffer(file, util.FSize(file), filename, btype, cmd) if buf == nil { return nil, errors.New("could not open file") } @@ -323,17 +335,18 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, // It will return an empty buffer if the path does not exist // and an error if the file is a directory func NewBufferFromFile(path string, btype BufType) (*Buffer, error) { - return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1}) + return NewBufferFromFileWithCommand(path, btype, emptyCommand) } -// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc -func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer { - return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype) +// NewBufferFromStringWithCommand creates a new buffer containing the given string +// with a cursor loc and a search text +func NewBufferFromStringWithCommand(text, path string, btype BufType, cmd Command) *Buffer { + return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, cmd) } // NewBufferFromString creates a new buffer containing the given string func NewBufferFromString(text, path string, btype BufType) *Buffer { - return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype) + return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, emptyCommand) } // NewBuffer creates a new buffer from a given reader with a given path @@ -341,7 +354,7 @@ func NewBufferFromString(text, path string, btype BufType) *Buffer { // a new buffer // Places the cursor at startcursor. If startcursor is -1, -1 places the // cursor at an autodetected location (based on savecursor or :LINE:COL) -func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer { +func NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) *Buffer { absPath, err := filepath.Abs(path) if err != nil { absPath = path @@ -436,8 +449,8 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm) } - if startcursor.X != -1 && startcursor.Y != -1 { - b.StartCursor = startcursor + if cmd.StartCursor.X != -1 && cmd.StartCursor.Y != -1 { + b.StartCursor = cmd.StartCursor } else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) { err := b.Unserialize() if err != nil { @@ -448,6 +461,23 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT b.AddCursor(NewCursor(b, b.StartCursor)) b.GetActiveCursor().Relocate() + if cmd.SearchRegex != "" { + match, found, _ := b.FindNext(cmd.SearchRegex, b.Start(), b.End(), b.StartCursor, true, true) + if found { + if cmd.SearchAfterStart { + // Search from current cursor and move it accordingly + b.GetActiveCursor().SetSelectionStart(match[0]) + b.GetActiveCursor().SetSelectionEnd(match[1]) + b.GetActiveCursor().OrigSelection[0] = b.GetActiveCursor().CurSelection[0] + b.GetActiveCursor().OrigSelection[1] = b.GetActiveCursor().CurSelection[1] + b.GetActiveCursor().GotoLoc(match[1]) + } + b.LastSearch = cmd.SearchRegex + b.LastSearchRegex = true + b.HighlightSearch = b.Settings["hlsearch"].(bool) + } + } + if !b.Settings["fastdirty"].(bool) && !found { if size > LargeFileThreshold { // If the file is larger than LargeFileThreshold fastdirty needs to be on