mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-09 22:30:24 +09:00
The -l flag to the replace command means "literal" and will treat the search term literally instead of as a regular expression. The command bar also now supports expanding environment variables and running expressions through the shell and using the result in the command.
343 lines
7.4 KiB
Go
343 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/mattn/go-runewidth"
|
|
"github.com/mattn/go-shellwords"
|
|
homedir "github.com/mitchellh/go-homedir"
|
|
)
|
|
|
|
// Util.go is a collection of utility functions that are used throughout
|
|
// the program
|
|
|
|
// Count returns the length of a string in runes
|
|
// This is exactly equivalent to utf8.RuneCountInString(), just less characters
|
|
func Count(s string) int {
|
|
return utf8.RuneCountInString(s)
|
|
}
|
|
|
|
// NumOccurrences counts the number of occurrences of a byte in a string
|
|
func NumOccurrences(s string, c byte) int {
|
|
var n int
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == c {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Spaces returns a string with n spaces
|
|
func Spaces(n int) string {
|
|
return strings.Repeat(" ", n)
|
|
}
|
|
|
|
// Min takes the min of two ints
|
|
func Min(a, b int) int {
|
|
if a > b {
|
|
return b
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Max takes the max of two ints
|
|
func Max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// FSize gets the size of a file
|
|
func FSize(f *os.File) int64 {
|
|
fi, _ := f.Stat()
|
|
// get the size
|
|
return fi.Size()
|
|
}
|
|
|
|
// IsWordChar returns whether or not the string is a 'word character'
|
|
// If it is a unicode character, then it does not match
|
|
// Word characters are defined as [A-Za-z0-9_]
|
|
func IsWordChar(str string) bool {
|
|
if len(str) > 1 {
|
|
// Unicode
|
|
return true
|
|
}
|
|
c := str[0]
|
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
|
|
}
|
|
|
|
// IsWhitespace returns true if the given rune is a space, tab, or newline
|
|
func IsWhitespace(c rune) bool {
|
|
return c == ' ' || c == '\t' || c == '\n'
|
|
}
|
|
|
|
// IsStrWhitespace returns true if the given string is all whitespace
|
|
func IsStrWhitespace(str string) bool {
|
|
for _, c := range str {
|
|
if !IsWhitespace(c) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Contains returns whether or not a string array contains a given string
|
|
func Contains(list []string, a string) bool {
|
|
for _, b := range list {
|
|
if b == a {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Insert makes a simple insert into a string at the given position
|
|
func Insert(str string, pos int, value string) string {
|
|
return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
|
|
}
|
|
|
|
// MakeRelative will attempt to make a relative path between path and base
|
|
func MakeRelative(path, base string) (string, error) {
|
|
if len(path) > 0 {
|
|
rel, err := filepath.Rel(base, path)
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
return rel, nil
|
|
}
|
|
return path, nil
|
|
}
|
|
|
|
// GetLeadingWhitespace returns the leading whitespace of the given string
|
|
func GetLeadingWhitespace(str string) string {
|
|
ws := ""
|
|
for _, c := range str {
|
|
if c == ' ' || c == '\t' {
|
|
ws += string(c)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return ws
|
|
}
|
|
|
|
// IsSpaces checks if a given string is only spaces
|
|
func IsSpaces(str string) bool {
|
|
for _, c := range str {
|
|
if c != ' ' {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// IsSpacesOrTabs checks if a given string contains only spaces and tabs
|
|
func IsSpacesOrTabs(str string) bool {
|
|
for _, c := range str {
|
|
if c != ' ' && c != '\t' {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
|
|
// as 'true' and 'false' respectively
|
|
func ParseBool(str string) (bool, error) {
|
|
if str == "on" {
|
|
return true, nil
|
|
}
|
|
if str == "off" {
|
|
return false, nil
|
|
}
|
|
return strconv.ParseBool(str)
|
|
}
|
|
|
|
// EscapePath replaces every path separator in a given path with a %
|
|
func EscapePath(path string) string {
|
|
path = filepath.ToSlash(path)
|
|
return strings.Replace(path, "/", "%", -1)
|
|
}
|
|
|
|
// GetModTime returns the last modification time for a given file
|
|
// It also returns a boolean if there was a problem accessing the file
|
|
func GetModTime(path string) (time.Time, bool) {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return time.Now(), false
|
|
}
|
|
return info.ModTime(), true
|
|
}
|
|
|
|
// StringWidth returns the width of a string where tabs count as `tabsize` width
|
|
func StringWidth(str string, tabsize int) int {
|
|
sw := runewidth.StringWidth(str)
|
|
lineIdx := 0
|
|
for _, ch := range str {
|
|
switch ch {
|
|
case '\t':
|
|
ts := tabsize - (lineIdx % tabsize)
|
|
sw += ts
|
|
lineIdx += ts
|
|
case '\n':
|
|
lineIdx = 0
|
|
default:
|
|
lineIdx++
|
|
}
|
|
}
|
|
return sw
|
|
}
|
|
|
|
// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
|
|
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
|
|
func WidthOfLargeRunes(str string, tabsize int) int {
|
|
count := 0
|
|
lineIdx := 0
|
|
for _, ch := range str {
|
|
var w int
|
|
if ch == '\t' {
|
|
w = tabsize - (lineIdx % tabsize)
|
|
} else {
|
|
w = runewidth.RuneWidth(ch)
|
|
}
|
|
if w > 1 {
|
|
count += (w - 1)
|
|
}
|
|
if ch == '\n' {
|
|
lineIdx = 0
|
|
} else {
|
|
lineIdx += w
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// RunePos returns the rune index of a given byte index
|
|
// This could cause problems if the byte index is between code points
|
|
func runePos(p int, str string) int {
|
|
return utf8.RuneCountInString(str[:p])
|
|
}
|
|
|
|
func lcs(a, b string) string {
|
|
arunes := []rune(a)
|
|
brunes := []rune(b)
|
|
|
|
lcs := ""
|
|
for i, r := range arunes {
|
|
if i >= len(brunes) {
|
|
break
|
|
}
|
|
if r == brunes[i] {
|
|
lcs += string(r)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return lcs
|
|
}
|
|
|
|
// CommonSubstring gets a common substring among the inputs
|
|
func CommonSubstring(arr ...string) string {
|
|
commonStr := arr[0]
|
|
|
|
for _, str := range arr[1:] {
|
|
commonStr = lcs(commonStr, str)
|
|
}
|
|
|
|
return commonStr
|
|
}
|
|
|
|
// Abs is a simple absolute value function for ints
|
|
func Abs(n int) int {
|
|
if n < 0 {
|
|
return -n
|
|
}
|
|
return n
|
|
}
|
|
|
|
// FuncName returns the full name of a given function object
|
|
func FuncName(i interface{}) string {
|
|
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
|
}
|
|
|
|
// ShortFuncName returns the name only of a given function object
|
|
func ShortFuncName(i interface{}) string {
|
|
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
|
|
}
|
|
|
|
// SplitCommandArgs separates multiple command arguments which may be quoted.
|
|
// The returned slice contains at least one string
|
|
func SplitCommandArgs(input string) ([]string, error) {
|
|
shellwords.ParseEnv = true
|
|
shellwords.ParseBacktick = true
|
|
return shellwords.Parse(input)
|
|
}
|
|
|
|
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
|
|
func JoinCommandArgs(args ...string) string {
|
|
var buf bytes.Buffer
|
|
for i, w := range args {
|
|
if i != 0 {
|
|
buf.WriteByte(' ')
|
|
}
|
|
if w == "" {
|
|
buf.WriteString("''")
|
|
continue
|
|
}
|
|
|
|
strBytes := []byte(w)
|
|
for _, b := range strBytes {
|
|
switch b {
|
|
case
|
|
'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
|
'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
|
'o', 'p', 'q', 'r', 's', 't', 'u',
|
|
'v', 'w', 'x', 'y', 'z',
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
|
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
|
'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
|
'V', 'W', 'X', 'Y', 'Z',
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
'_', '-', '.', ',', ':', '/', '@':
|
|
buf.WriteByte(b)
|
|
case '\n':
|
|
buf.WriteString("'\n'")
|
|
default:
|
|
buf.WriteByte('\\')
|
|
buf.WriteByte(b)
|
|
}
|
|
}
|
|
|
|
// return buf.String()
|
|
// buf.WriteString(w)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
|
|
// home directory. Does nothing if the path does not start with '~'.
|
|
func ReplaceHome(path string) string {
|
|
if !strings.HasPrefix(path, "~") {
|
|
return path
|
|
}
|
|
|
|
home, err := homedir.Dir()
|
|
if err != nil {
|
|
messenger.Error("Could not find home directory: ", err)
|
|
return path
|
|
}
|
|
return strings.Replace(path, "~", home, 1)
|
|
}
|