micro/internal/action/command.go

895 lines
21 KiB
Go
Raw Normal View History

2019-01-02 07:29:25 +03:00
package action
import (
2019-08-07 08:24:03 +03:00
"bytes"
2019-01-15 00:52:25 +03:00
"errors"
"fmt"
2019-01-02 07:29:25 +03:00
"os"
2019-12-19 18:22:31 +03:00
"os/exec"
2019-01-14 08:57:39 +03:00
"path/filepath"
2019-01-16 06:45:28 +03:00
"regexp"
2019-01-14 08:57:39 +03:00
"strconv"
"strings"
2019-01-16 06:45:28 +03:00
"unicode/utf8"
2019-01-02 07:29:25 +03:00
2020-01-03 02:30:51 +03:00
shellquote "github.com/kballard/go-shellquote"
2019-02-04 07:17:24 +03:00
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
2019-01-02 07:29:25 +03:00
)
2019-01-21 01:49:20 +03:00
// A Command contains information about how to execute a command
// It has the action for that command as well as a completer function
2019-01-02 07:29:25 +03:00
type Command struct {
2019-01-21 01:49:20 +03:00
action func(*BufPane, []string)
completer buffer.Completer
2019-01-02 07:29:25 +03:00
}
var commands map[string]Command
func InitCommands() {
2019-01-21 01:49:20 +03:00
commands = map[string]Command{
2019-12-30 06:02:14 +03:00
"set": {(*BufPane).SetCmd, OptionValueComplete},
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
"show": {(*BufPane).ShowCmd, OptionComplete},
"showkey": {(*BufPane).ShowKeyCmd, nil},
"run": {(*BufPane).RunCmd, nil},
"bind": {(*BufPane).BindCmd, nil},
"unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil},
"save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
"help": {(*BufPane).HelpCmd, HelpComplete},
"eval": {(*BufPane).EvalCmd, nil},
"log": {(*BufPane).ToggleLogCmd, nil},
"plugin": {(*BufPane).PluginCmd, PluginComplete},
"reload": {(*BufPane).ReloadCmd, nil},
"reopen": {(*BufPane).ReopenCmd, nil},
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
"retab": {(*BufPane).RetabCmd, nil},
"raw": {(*BufPane).RawCmd, nil},
"textfilter": {(*BufPane).TextFilterCmd, nil},
2019-01-02 07:29:25 +03:00
}
}
// MakeCommand is a function to easily create new commands
// This can be called by plugins in Lua so that plugins can define their own commands
func MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {
if action != nil {
commands[name] = Command{action, completer}
2019-08-04 01:19:28 +03:00
}
}
2019-01-02 07:29:25 +03:00
// CommandEditAction returns a bindable function that opens a prompt with
// the given string and executes the command when the user presses
// enter
func CommandEditAction(prompt string) BufKeyAction {
2019-01-19 23:37:59 +03:00
return func(h *BufPane) bool {
2019-01-04 01:07:28 +03:00
InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) {
2019-01-02 07:29:25 +03:00
if !canceled {
2019-01-11 22:49:22 +03:00
MainTab().CurPane().HandleCommand(resp)
2019-01-02 07:29:25 +03:00
}
})
return false
}
}
// CommandAction returns a bindable function which executes the
// given command
func CommandAction(cmd string) BufKeyAction {
2019-01-19 23:37:59 +03:00
return func(h *BufPane) bool {
2019-01-11 22:49:22 +03:00
MainTab().CurPane().HandleCommand(cmd)
2019-01-02 07:29:25 +03:00
return false
}
}
var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
2019-08-07 08:24:03 +03:00
2019-01-02 07:29:25 +03:00
// PluginCmd installs, removes, updates, lists, or searches for given plugins
2019-01-19 23:37:59 +03:00
func (h *BufPane) PluginCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments")
return
}
if h.Buf.Type != buffer.BTLog {
OpenLogBuf(h)
}
config.PluginCommand(buffer.LogBuf, args[0], args[1:])
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// RetabCmd changes all spaces to tabs or all tabs to spaces
2019-01-02 07:29:25 +03:00
// depending on the user's settings
2019-01-19 23:37:59 +03:00
func (h *BufPane) RetabCmd(args []string) {
2019-01-15 06:44:06 +03:00
h.Buf.Retab()
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// RawCmd opens a new raw view which displays the escape sequences micro
2019-01-02 07:29:25 +03:00
// is receiving in real-time
2019-01-19 23:37:59 +03:00
func (h *BufPane) RawCmd(args []string) {
width, height := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
2020-02-06 01:16:31 +03:00
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
2019-01-19 23:37:59 +03:00
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
2019-01-02 07:29:25 +03:00
}
2019-12-19 18:22:31 +03:00
// TextFilterCmd filters the selection through the command.
// Selection goes to the command input.
2019-12-30 06:02:14 +03:00
// On successful run command output replaces the current selection.
2019-12-19 18:22:31 +03:00
func (h *BufPane) TextFilterCmd(args []string) {
if len(args) == 0 {
InfoBar.Error("usage: textfilter arguments")
return
}
sel := h.Cursor.GetSelection()
if len(sel) == 0 {
h.Cursor.SelectWord()
sel = h.Cursor.GetSelection()
}
var bout, berr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = strings.NewReader(string(sel))
cmd.Stderr = &berr
cmd.Stdout = &bout
err := cmd.Run()
if err != nil {
InfoBar.Error(err.Error() + " " + berr.String())
return
}
h.Cursor.DeleteSelection()
h.Buf.Insert(h.Cursor.Loc, bout.String())
}
2019-01-11 22:49:22 +03:00
// TabSwitchCmd switches to a given tab either by name or by number
2019-01-19 23:37:59 +03:00
func (h *BufPane) TabSwitchCmd(args []string) {
2019-01-14 08:57:39 +03:00
if len(args) > 0 {
num, err := strconv.Atoi(args[0])
if err != nil {
// Check for tab with this name
found := false
for i, t := range Tabs.List {
if t.Panes[t.active].Name() == args[0] {
Tabs.SetActive(i)
found = true
}
}
if !found {
InfoBar.Error("Could not find tab: ", err)
}
} else {
num--
if num >= 0 && num < len(Tabs.List) {
Tabs.SetActive(num)
} else {
InfoBar.Error("Invalid tab index")
}
}
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// CdCmd changes the current working directory
2019-01-19 23:37:59 +03:00
func (h *BufPane) CdCmd(args []string) {
2019-01-14 08:57:39 +03:00
if len(args) > 0 {
path, err := util.ReplaceHome(args[0])
if err != nil {
InfoBar.Error(err)
return
}
err = os.Chdir(path)
if err != nil {
InfoBar.Error(err)
return
}
wd, _ := os.Getwd()
for _, b := range buffer.OpenBuffers {
if len(b.Path) > 0 {
b.Path, _ = util.MakeRelative(b.AbsPath, wd)
if p, _ := filepath.Abs(b.Path); !strings.Contains(p, wd) {
b.Path = b.AbsPath
}
}
}
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// MemUsageCmd prints micro's memory usage
2019-01-02 07:29:25 +03:00
// Alloc shows how many bytes are currently in use
// Sys shows how many bytes have been requested from the operating system
// NumGC shows how many times the GC has been run
// Note that Go commonly reserves more memory from the OS than is currently in-use/required
// Additionally, even if Go returns memory to the OS, the OS does not always claim it because
// there may be plenty of memory to spare
2019-01-19 23:37:59 +03:00
func (h *BufPane) MemUsageCmd(args []string) {
2019-01-02 07:29:25 +03:00
InfoBar.Message(util.GetMemStats())
}
2019-01-11 22:49:22 +03:00
// PwdCmd prints the current working directory
2019-01-19 23:37:59 +03:00
func (h *BufPane) PwdCmd(args []string) {
2019-01-02 07:29:25 +03:00
wd, err := os.Getwd()
if err != nil {
InfoBar.Message(err.Error())
} else {
InfoBar.Message(wd)
}
}
2019-01-11 22:49:22 +03:00
// OpenCmd opens a new buffer with a given filename
2019-01-19 23:37:59 +03:00
func (h *BufPane) OpenCmd(args []string) {
2019-01-14 08:57:39 +03:00
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
2020-01-03 02:30:51 +03:00
args, err := shellquote.Split(filename)
2019-01-14 08:57:39 +03:00
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
2019-01-14 08:57:39 +03:00
filename = strings.Join(args, " ")
open := func() {
b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
h.OpenBuffer(b)
}
if h.Buf.Modified() {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
open()
} else if !canceled && yes {
h.Save()
open()
}
})
} else {
open()
}
} else {
InfoBar.Error("No filename")
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// ToggleLogCmd toggles the log view
2019-01-19 23:37:59 +03:00
func (h *BufPane) ToggleLogCmd(args []string) {
2019-08-06 06:43:34 +03:00
if h.Buf.Type != buffer.BTLog {
OpenLogBuf(h)
} else {
h.Quit()
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// ReloadCmd reloads all files (syntax files, colorschemes...)
2019-01-19 23:37:59 +03:00
func (h *BufPane) ReloadCmd(args []string) {
2019-08-04 02:46:51 +03:00
ReloadConfig()
}
func ReloadConfig() {
config.InitRuntimeFiles()
err := config.ReadSettings()
if err != nil {
screen.TermMessage(err)
}
config.InitGlobalSettings()
InitBindings()
InitCommands()
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
}
2019-01-02 07:29:25 +03:00
}
// ReopenCmd reopens the buffer (reload from disk)
func (h *BufPane) ReopenCmd(args []string) {
if h.Buf.Modified() {
InfoBar.YNPrompt("Save file before reopen?", func(yes, canceled bool) {
if !canceled && yes {
h.Save()
h.Buf.ReOpen()
} else if !canceled {
h.Buf.ReOpen()
}
})
} else {
h.Buf.ReOpen()
}
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) openHelp(page string) error {
2019-01-15 00:52:25 +03:00
if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err))
} else {
helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
helpBuffer.SetName("Help " + page)
if h.Buf.Type == buffer.BTHelp {
h.OpenBuffer(helpBuffer)
} else {
h.HSplitBuf(helpBuffer)
}
}
return nil
}
2019-01-11 22:49:22 +03:00
// HelpCmd tries to open the given help page in a horizontal split
2019-01-19 23:37:59 +03:00
func (h *BufPane) HelpCmd(args []string) {
2019-01-15 00:52:25 +03:00
if len(args) < 1 {
// Open the default help if the user just typed "> help"
h.openHelp("help")
} else {
if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
err := h.openHelp(args[0])
if err != nil {
InfoBar.Error(err)
}
} else {
InfoBar.Error("Sorry, no help for ", args[0])
}
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// VSplitCmd opens a vertical split with file given in the first argument
2019-01-02 07:29:25 +03:00
// If no file is given, it opens an empty buffer in a new split
2019-01-19 23:37:59 +03:00
func (h *BufPane) VSplitCmd(args []string) {
2019-01-14 02:18:23 +03:00
if len(args) == 0 {
// Open an empty vertical split
h.VSplitAction()
return
}
2019-01-05 01:40:56 +03:00
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
2019-01-14 02:18:23 +03:00
h.VSplitBuf(buf)
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// HSplitCmd opens a horizontal split with file given in the first argument
2019-01-02 07:29:25 +03:00
// If no file is given, it opens an empty buffer in a new split
2019-01-19 23:37:59 +03:00
func (h *BufPane) HSplitCmd(args []string) {
2019-01-14 02:18:23 +03:00
if len(args) == 0 {
// Open an empty horizontal split
h.HSplitAction()
return
}
2019-01-05 01:40:56 +03:00
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
2019-01-14 02:18:23 +03:00
h.HSplitBuf(buf)
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// EvalCmd evaluates a lua expression
2019-01-19 23:37:59 +03:00
func (h *BufPane) EvalCmd(args []string) {
2020-01-02 23:10:28 +03:00
InfoBar.Error("Eval unsupported")
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// NewTabCmd opens the given file in a new tab
2019-01-19 23:37:59 +03:00
func (h *BufPane) NewTabCmd(args []string) {
2019-01-11 22:49:22 +03:00
width, height := screen.Screen.Size()
2019-01-15 06:16:44 +03:00
iOffset := config.GetInfoBarOffset()
2019-01-11 22:49:22 +03:00
if len(args) > 0 {
for _, a := range args {
b, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
2019-01-15 06:16:44 +03:00
tp := NewTabFromBuffer(0, 0, width, height-1-iOffset, b)
2019-01-11 22:49:22 +03:00
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
}
} else {
b := buffer.NewBufferFromString("", "", buffer.BTDefault)
2019-01-15 06:16:44 +03:00
tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
2019-01-11 22:49:22 +03:00
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
}
2019-01-02 07:29:25 +03:00
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
2020-01-21 06:03:32 +03:00
local := false
for _, s := range config.LocalSettings {
if s == option {
local = true
break
2019-01-14 05:06:58 +03:00
}
2020-01-21 06:03:32 +03:00
}
if !local {
config.GlobalSettings[option] = nativeValue
if option == "colorscheme" {
// LoadSyntaxFiles()
config.InitColorscheme()
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
}
} else if option == "infobar" || option == "keymenu" {
Tabs.Resize()
} else if option == "mouse" {
if !nativeValue.(bool) {
screen.Screen.DisableMouse()
} else {
screen.Screen.EnableMouse()
}
} else if option == "autosave" {
if nativeValue.(float64) > 0 {
config.SetAutoTime(int(nativeValue.(float64)))
config.StartAutoSave()
} else {
config.SetAutoTime(0)
}
2020-01-21 06:03:32 +03:00
} else if option == "paste" {
screen.Screen.SetPaste(nativeValue.(bool))
2019-01-14 05:06:58 +03:00
} else {
2020-01-21 06:03:32 +03:00
for _, pl := range config.Plugins {
if option == pl.Name {
if nativeValue.(bool) && !pl.Loaded {
pl.Load()
_, err := pl.Call("init")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
} else if !nativeValue.(bool) && pl.Loaded {
_, err := pl.Call("deinit")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
2019-08-03 09:46:25 +03:00
}
}
}
2019-01-14 05:06:58 +03:00
}
for _, b := range buffer.OpenBuffers {
b.SetOptionNative(option, nativeValue)
2019-01-14 05:06:58 +03:00
}
2020-02-11 21:09:17 +03:00
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
}
2019-01-14 05:06:58 +03:00
func SetGlobalOption(option, value string) error {
if _, ok := config.GlobalSettings[option]; !ok {
return config.ErrInvalidOption
}
nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
if err != nil {
return err
}
return SetGlobalOptionNative(option, nativeValue)
}
// ResetCmd resets a setting to its default value
func (h *BufPane) ResetCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments")
return
}
option := args[0]
defaultGlobals := config.DefaultGlobalSettings()
2019-08-04 03:12:23 +03:00
defaultLocals := config.DefaultCommonSettings()
if _, ok := defaultGlobals[option]; ok {
SetGlobalOptionNative(option, defaultGlobals[option])
return
}
if _, ok := defaultLocals[option]; ok {
h.Buf.SetOptionNative(option, defaultLocals[option])
return
}
InfoBar.Error(config.ErrInvalidOption)
2019-01-14 05:06:58 +03:00
}
2019-01-11 22:49:22 +03:00
// SetCmd sets an option
2019-01-19 23:37:59 +03:00
func (h *BufPane) SetCmd(args []string) {
2019-01-14 05:06:58 +03:00
if len(args) < 2 {
InfoBar.Error("Not enough arguments")
return
}
option := args[0]
value := args[1]
err := SetGlobalOption(option, value)
if err == config.ErrInvalidOption {
err := h.Buf.SetOption(option, value)
if err != nil {
InfoBar.Error(err)
}
} else if err != nil {
InfoBar.Error(err)
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// SetLocalCmd sets an option local to the buffer
2019-01-19 23:37:59 +03:00
func (h *BufPane) SetLocalCmd(args []string) {
2019-01-14 05:06:58 +03:00
if len(args) < 2 {
InfoBar.Error("Not enough arguments")
return
}
option := args[0]
value := args[1]
err := h.Buf.SetOption(option, value)
if err != nil {
InfoBar.Error(err)
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// ShowCmd shows the value of the given option
2019-01-19 23:37:59 +03:00
func (h *BufPane) ShowCmd(args []string) {
2019-01-14 08:57:39 +03:00
if len(args) < 1 {
InfoBar.Error("Please provide an option to show")
return
}
var option interface{}
if opt, ok := h.Buf.Settings[args[0]]; ok {
option = opt
} else if opt, ok := config.GlobalSettings[args[0]]; ok {
option = opt
}
if option == nil {
InfoBar.Error(args[0], " is not a valid option")
return
}
InfoBar.Message(option)
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// ShowKeyCmd displays the action that a key is bound to
2019-01-19 23:37:59 +03:00
func (h *BufPane) ShowKeyCmd(args []string) {
2019-01-09 23:17:51 +03:00
if len(args) < 1 {
InfoBar.Error("Please provide a key to show")
return
}
2019-01-11 23:33:16 +03:00
if action, ok := config.Bindings[args[0]]; ok {
2019-01-09 23:17:51 +03:00
InfoBar.Message(action)
} else {
InfoBar.Message(args[0], " has no binding")
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// BindCmd creates a new keybinding
2019-01-19 23:37:59 +03:00
func (h *BufPane) BindCmd(args []string) {
2019-01-15 00:09:46 +03:00
if len(args) < 2 {
InfoBar.Error("Not enough arguments")
return
}
_, err := TryBindKey(args[0], args[1], true)
if err != nil {
InfoBar.Error(err)
}
}
// UnbindCmd binds a key to its default action
2019-01-19 23:37:59 +03:00
func (h *BufPane) UnbindCmd(args []string) {
2019-01-15 00:09:46 +03:00
if len(args) < 1 {
2019-12-30 06:02:14 +03:00
InfoBar.Error("Not enough arguments")
2019-01-15 00:09:46 +03:00
return
}
err := UnbindKey(args[0])
if err != nil {
InfoBar.Error(err)
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// RunCmd runs a shell command in the background
2019-01-19 23:37:59 +03:00
func (h *BufPane) RunCmd(args []string) {
2020-01-03 02:30:51 +03:00
runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
2019-01-11 00:37:05 +03:00
if err != nil {
InfoBar.Error(err)
} else {
go func() {
InfoBar.Message(runf())
screen.Redraw()
}()
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// QuitCmd closes the main view
2019-01-19 23:37:59 +03:00
func (h *BufPane) QuitCmd(args []string) {
2019-01-14 08:57:39 +03:00
h.Quit()
2019-01-02 07:29:25 +03:00
}
2019-08-04 02:07:16 +03:00
// GotoCmd is a command that will send the cursor to a certain
// position in the buffer
// For example: `goto line`, or `goto line:col`
func (h *BufPane) GotoCmd(args []string) {
if len(args) <= 0 {
InfoBar.Error("Not enough arguments")
} else {
h.RemoveAllMultiCursors()
if strings.Contains(args[0], ":") {
parts := strings.SplitN(args[0], ":", 2)
line, err := strconv.Atoi(parts[0])
if err != nil {
InfoBar.Error(err)
return
}
col, err := strconv.Atoi(parts[1])
if err != nil {
InfoBar.Error(err)
return
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
h.Cursor.GotoLoc(buffer.Loc{col, line})
} else {
line, err := strconv.Atoi(args[0])
if err != nil {
InfoBar.Error(err)
return
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
h.Cursor.GotoLoc(buffer.Loc{0, line})
}
2019-12-22 00:12:51 +03:00
h.Relocate()
2019-08-04 02:07:16 +03:00
}
}
// SaveCmd saves the buffer optionally with an argument file name
2019-01-19 23:37:59 +03:00
func (h *BufPane) SaveCmd(args []string) {
if len(args) == 0 {
h.Save()
} else {
h.Buf.SaveAs(args[0])
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// ReplaceCmd runs search and replace
2019-01-19 23:37:59 +03:00
func (h *BufPane) ReplaceCmd(args []string) {
2019-01-16 06:45:28 +03:00
if len(args) < 2 || len(args) > 4 {
// We need to find both a search and replace expression
InfoBar.Error("Invalid replace statement: " + strings.Join(args, " "))
return
}
all := false
noRegex := false
2019-08-12 00:50:28 +03:00
foundSearch := false
foundReplace := false
var search string
var replaceStr string
for _, arg := range args {
switch arg {
case "-a":
all = true
case "-l":
noRegex = true
default:
if !foundSearch {
foundSearch = true
search = arg
} else if !foundReplace {
foundReplace = true
replaceStr = arg
} else {
2019-01-16 06:45:28 +03:00
InfoBar.Error("Invalid flag: " + arg)
return
}
}
}
if noRegex {
search = regexp.QuoteMeta(search)
}
2019-08-12 00:50:28 +03:00
replace := []byte(replaceStr)
2019-01-16 06:45:28 +03:00
var regex *regexp.Regexp
var err error
if h.Buf.Settings["ignorecase"].(bool) {
regex, err = regexp.Compile("(?im)" + search)
} else {
regex, err = regexp.Compile("(?m)" + search)
}
if err != nil {
// There was an error with the user's regex
InfoBar.Error(err)
return
}
nreplaced := 0
start := h.Buf.Start()
2019-12-27 08:06:02 +03:00
// end := h.Buf.End()
// if h.Cursor.HasSelection() {
// start = h.Cursor.CurSelection[0]
// end = h.Cursor.CurSelection[1]
// }
2019-01-16 06:45:28 +03:00
if all {
2019-12-27 08:06:02 +03:00
nreplaced = h.Buf.ReplaceRegex(start, h.Buf.End(), regex, replace)
2019-01-16 06:45:28 +03:00
} else {
inRange := func(l buffer.Loc) bool {
2019-12-27 08:06:02 +03:00
return l.GreaterEqual(start) && l.LessEqual(h.Buf.End())
2019-01-16 06:45:28 +03:00
}
searchLoc := start
searching := true
var doReplacement func()
doReplacement = func() {
2019-12-27 08:06:02 +03:00
locs, found, err := h.Buf.FindNext(search, start, h.Buf.End(), searchLoc, true, !noRegex)
2019-01-16 06:45:28 +03:00
if err != nil {
InfoBar.Error(err)
return
}
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
h.Cursor.ResetSelection()
2019-01-25 02:25:59 +03:00
h.Buf.RelocateCursors()
2019-01-16 06:45:28 +03:00
return
}
h.Cursor.SetSelectionStart(locs[0])
h.Cursor.SetSelectionEnd(locs[1])
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
if !canceled && yes {
h.Buf.Replace(locs[0], locs[1], replaceStr)
2019-12-27 08:06:02 +03:00
2019-01-16 06:45:28 +03:00
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
h.Cursor.Loc = searchLoc
nreplaced++
} else if !canceled && !yes {
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
} else if canceled {
h.Cursor.ResetSelection()
2019-01-25 02:25:59 +03:00
h.Buf.RelocateCursors()
2019-01-16 06:45:28 +03:00
return
}
if searching {
doReplacement()
}
})
}
doReplacement()
}
2019-01-25 02:25:59 +03:00
h.Buf.RelocateCursors()
2019-01-16 06:45:28 +03:00
if nreplaced > 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
} else if nreplaced == 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
} else {
InfoBar.Message("Nothing matched ", search)
}
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// ReplaceAllCmd replaces search term all at once
2019-01-19 23:37:59 +03:00
func (h *BufPane) ReplaceAllCmd(args []string) {
2019-01-19 23:53:02 +03:00
// aliased to Replace command
h.ReplaceCmd(append(args, "-a"))
2019-01-02 07:29:25 +03:00
}
2019-01-11 22:49:22 +03:00
// TermCmd opens a terminal in the current view
2019-01-19 23:37:59 +03:00
func (h *BufPane) TermCmd(args []string) {
2020-02-06 01:16:31 +03:00
ps := h.tab.Panes
2019-01-11 22:49:22 +03:00
if !TermEmuSupported {
InfoBar.Error("Terminal emulator not supported on this system")
return
}
2019-01-13 06:58:16 +03:00
if len(args) == 0 {
sh := os.Getenv("SHELL")
if sh == "" {
InfoBar.Error("Shell environment not found")
return
}
args = []string{sh}
}
2019-01-15 08:24:53 +03:00
term := func(i int, newtab bool) {
2019-01-11 22:49:22 +03:00
t := new(shell.Terminal)
t.Start(args, false, true, nil, nil)
2019-01-14 02:18:23 +03:00
id := h.ID()
if newtab {
h.AddTab()
i = 0
id = MainTab().Panes[0].ID()
2019-01-14 05:06:58 +03:00
} else {
MainTab().Panes[i].Close()
2019-01-14 02:18:23 +03:00
}
v := h.GetView()
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
InfoBar.Error(err)
return
}
MainTab().Panes[i] = tp
2019-01-11 22:49:22 +03:00
MainTab().SetActive(i)
}
2019-01-15 08:24:53 +03:00
// If there is only one open file we make a new tab instead of overwriting it
newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
if newtab {
term(0, true)
return
}
2019-01-11 22:49:22 +03:00
for i, p := range ps {
if p.ID() == h.ID() {
if h.Buf.Modified() {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
2019-01-15 08:24:53 +03:00
term(i, false)
2019-01-11 22:49:22 +03:00
} else if !canceled && yes {
h.Save()
2019-01-15 08:24:53 +03:00
term(i, false)
2019-01-11 22:49:22 +03:00
}
})
} else {
2019-01-15 08:24:53 +03:00
term(i, false)
2019-01-11 22:49:22 +03:00
}
}
}
2019-01-02 07:29:25 +03:00
}
// HandleCommand handles input from the user
2019-01-19 23:37:59 +03:00
func (h *BufPane) HandleCommand(input string) {
2020-01-03 02:30:51 +03:00
args, err := shellquote.Split(input)
2019-01-02 07:29:25 +03:00
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
2019-01-02 07:29:25 +03:00
inputCmd := args[0]
if _, ok := commands[inputCmd]; !ok {
InfoBar.Error("Unknown command ", inputCmd)
} else {
2019-08-06 06:43:34 +03:00
WriteLog("> " + input + "\n")
2019-01-11 22:49:22 +03:00
commands[inputCmd].action(h, args[1:])
2019-08-06 06:43:34 +03:00
WriteLog("\n")
2019-01-02 07:29:25 +03:00
}
}