<commit_before>package main

import (
	"bytes"
	"os"
	"os/exec"
	"os/signal"
	"regexp"
	"strings"
	"strconv"
)

var commands map[string]func([]string)

var commandActions = map[string]func([]string){
	"Set":     Set,
	"Run":     Run,
	"Bind":    Bind,
	"Quit":    Quit,
	"Save":    Save,
	"Replace": Replace,
}

// InitCommands initializes the default commands
func InitCommands() {
	commands = make(map[string]func([]string))

	defaults := DefaultCommands()
	parseCommands(defaults)
}

func parseCommands(userCommands map[string]string) {
	for k, v := range userCommands {
		MakeCommand(k, v)
	}
}

// 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, function string) {
	action := commandActions[function]
	if _, ok := commandActions[function]; !ok {
		// If the user seems to be binding a function that doesn't exist
		// We hope that it's a lua function that exists and bind it to that
		action = LuaFunctionCommand(function)
	}

	commands[name] = action
}

// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]string {
	return map[string]string{
		"set":     "Set",
		"bind":    "Bind",
		"run":     "Run",
		"quit":    "Quit",
		"save":    "Save",
		"replace": "Replace",
	}
}

// Set sets an option
func Set(args []string) {
	// Set an option and we have to set it for every view
	for _, tab := range tabs {
		for _, view := range tab.views {
			SetOption(view, args)
		}
	}
}

// Bind creates a new keybinding
func Bind(args []string) {
	if len(args) != 2 {
		messenger.Error("Incorrect number of arguments")
		return
	}
	BindKey(args[0], args[1])
}

// Run runs a shell command in the background
func Run(args []string) {
	// Run a shell command in the background (openTerm is false)
	HandleShellCommand(strings.Join(args, " "), false)
}

// Quit closes the main view
func Quit(args []string) {
	// Close the main view
	CurView().Quit()
}

// Save saves the buffer in the main view
func Save(args []string) {
	// Save the main view
	CurView().Save()
}

// Replace runs search and replace
func Replace(args []string) {
	// This is a regex to parse the replace expression
	// We allow no quotes if there are no spaces, but if you want to search
	// for or replace an expression with spaces, you can add double quotes
	r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
	replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
	if len(replaceCmd) < 2 {
		// We need to find both a search and replace expression
		messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
		return
	}

	var flags string
	if len(replaceCmd) == 3 {
		// The user included some flags
		flags = replaceCmd[2]
	}

	search := string(replaceCmd[0])
	replace := string(replaceCmd[1])

	// If the search and replace expressions have quotes, we need to remove those
	if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
		search = search[1 : len(search)-1]
	}
	if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
		replace = replace[1 : len(replace)-1]
	}

	// We replace all escaped double quotes to real double quotes
	search = strings.Replace(search, `\"`, `"`, -1)
	replace = strings.Replace(replace, `\"`, `"`, -1)
	// Replace some things so users can actually insert newlines and tabs in replacements
	replace = strings.Replace(replace, "\\n", "\n", -1)
	replace = strings.Replace(replace, "\\t", "\t", -1)

	regex, err := regexp.Compile(search)
	if err != nil {
		// There was an error with the user's regex
		messenger.Error(err.Error())
		return
	}

	view := CurView()

	found := false
	count := 0
	for {
		match := regex.FindStringIndex(view.Buf.String())
		if match == nil {
			break
		}
		found = true
		count = count + 1
		if strings.Contains(flags, "c") {
			// The 'check' flag was used
			Search(search, view, true)
			view.Relocate()
			if settings["syntax"].(bool) {
				view.matches = Match(view)
			}
			RedrawAll()
			choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
			if canceled {
				if view.Cursor.HasSelection() {
					view.Cursor.Loc = view.Cursor.CurSelection[0]
					view.Cursor.ResetSelection()
				}
				messenger.Reset()
				return
			}
			if choice {
				view.Cursor.DeleteSelection()
				view.Buf.Insert(FromCharPos(match[0], view.Buf), replace)
				view.Cursor.ResetSelection()
				messenger.Reset()
			} else {
				if view.Cursor.HasSelection() {
					searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
				} else {
					searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
				}
				continue
			}
		} else {
			view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
		}
	}
	if found {
		messenger.Message("Replaced " + strconv.Itoa(count) + " occurences")
	} else {
		messenger.Message("Nothing matched " + search)
	}
}

// RunShellCommand executes a shell command and returns the output/error
func RunShellCommand(input string) (string, error) {<commit_msg>command.go: Simplify and prettify replace feedback<commit_after>package main

import (
	"bytes"
	"os"
	"os/exec"
	"os/signal"
	"regexp"
	"strings"
	"strconv"
)

var commands map[string]func([]string)

var commandActions = map[string]func([]string){
	"Set":     Set,
	"Run":     Run,
	"Bind":    Bind,
	"Quit":    Quit,
	"Save":    Save,
	"Replace": Replace,
}

// InitCommands initializes the default commands
func InitCommands() {
	commands = make(map[string]func([]string))

	defaults := DefaultCommands()
	parseCommands(defaults)
}

func parseCommands(userCommands map[string]string) {
	for k, v := range userCommands {
		MakeCommand(k, v)
	}
}

// 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, function string) {
	action := commandActions[function]
	if _, ok := commandActions[function]; !ok {
		// If the user seems to be binding a function that doesn't exist
		// We hope that it's a lua function that exists and bind it to that
		action = LuaFunctionCommand(function)
	}

	commands[name] = action
}

// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]string {
	return map[string]string{
		"set":     "Set",
		"bind":    "Bind",
		"run":     "Run",
		"quit":    "Quit",
		"save":    "Save",
		"replace": "Replace",
	}
}

// Set sets an option
func Set(args []string) {
	// Set an option and we have to set it for every view
	for _, tab := range tabs {
		for _, view := range tab.views {
			SetOption(view, args)
		}
	}
}

// Bind creates a new keybinding
func Bind(args []string) {
	if len(args) != 2 {
		messenger.Error("Incorrect number of arguments")
		return
	}
	BindKey(args[0], args[1])
}

// Run runs a shell command in the background
func Run(args []string) {
	// Run a shell command in the background (openTerm is false)
	HandleShellCommand(strings.Join(args, " "), false)
}

// Quit closes the main view
func Quit(args []string) {
	// Close the main view
	CurView().Quit()
}

// Save saves the buffer in the main view
func Save(args []string) {
	// Save the main view
	CurView().Save()
}

// Replace runs search and replace
func Replace(args []string) {
	// This is a regex to parse the replace expression
	// We allow no quotes if there are no spaces, but if you want to search
	// for or replace an expression with spaces, you can add double quotes
	r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
	replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
	if len(replaceCmd) < 2 {
		// We need to find both a search and replace expression
		messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
		return
	}

	var flags string
	if len(replaceCmd) == 3 {
		// The user included some flags
		flags = replaceCmd[2]
	}

	search := string(replaceCmd[0])
	replace := string(replaceCmd[1])

	// If the search and replace expressions have quotes, we need to remove those
	if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
		search = search[1 : len(search)-1]
	}
	if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
		replace = replace[1 : len(replace)-1]
	}

	// We replace all escaped double quotes to real double quotes
	search = strings.Replace(search, `\"`, `"`, -1)
	replace = strings.Replace(replace, `\"`, `"`, -1)
	// Replace some things so users can actually insert newlines and tabs in replacements
	replace = strings.Replace(replace, "\\n", "\n", -1)
	replace = strings.Replace(replace, "\\t", "\t", -1)

	regex, err := regexp.Compile(search)
	if err != nil {
		// There was an error with the user's regex
		messenger.Error(err.Error())
		return
	}

	view := CurView()

	found := 0
	for {
		match := regex.FindStringIndex(view.Buf.String())
		if match == nil {
			break
		}
		found = found + 1
		if strings.Contains(flags, "c") {
			// The 'check' flag was used
			Search(search, view, true)
			view.Relocate()
			if settings["syntax"].(bool) {
				view.matches = Match(view)
			}
			RedrawAll()
			choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
			if canceled {
				if view.Cursor.HasSelection() {
					view.Cursor.Loc = view.Cursor.CurSelection[0]
					view.Cursor.ResetSelection()
				}
				messenger.Reset()
				return
			}
			if choice {
				view.Cursor.DeleteSelection()
				view.Buf.Insert(FromCharPos(match[0], view.Buf), replace)
				view.Cursor.ResetSelection()
				messenger.Reset()
			} else {
				if view.Cursor.HasSelection() {
					searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
				} else {
					searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
				}
				continue
			}
		} else {
			view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
		}
	}
	if found > 1 {
		messenger.Message("Replaced " + strconv.Itoa(found) + " occurences of " + search)
	} else if found == 1 {
		messenger.Message("Replaced " + strconv.Itoa(found) + " occurence of " + search)
	} else {
		messenger.Message("Nothing matched " + search)
	}
}

// RunShellCommand executes a shell command and returns the output/error
func RunShellCommand(input string) (string, error) {
{'fraction_non_alphanumeric': 0.1417169554214522, 'fraction_numerical': 0.004866653688923496, 'mean_word_length': 3.540433053468847, 'pattern_counts': {'":': 24, '<': 5, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5589856', 'n_tokens_mistral': 3680, 'n_tokens_neox': 3352, 'n_words': 1013}