X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Futil.go;h=833562941436689ca668456c41ef51c55c89fe06;hb=ea6a87d41a9fcaa9fff81c1a5310726b1e15c548;hp=db452e062248b0f92e8f152fb298181998f0bc44;hpb=72f5808025af2d3d24bf1ccca899f7d291cb1dda;p=micro.git diff --git a/cmd/micro/util.go b/cmd/micro/util.go index db452e06..83356294 100644 --- a/cmd/micro/util.go +++ b/cmd/micro/util.go @@ -3,12 +3,15 @@ package main import ( "os" "path/filepath" + "reflect" + "runtime" "strconv" "strings" "time" "unicode/utf8" "github.com/mattn/go-runewidth" + homedir "github.com/mitchellh/go-homedir" ) // Util.go is a collection of utility functions that are used throughout @@ -20,8 +23,56 @@ func Count(s string) int { return utf8.RuneCountInString(s) } -// NumOccurences counts the number of occurences of a byte in a string -func NumOccurences(s string, c byte) int { +// Convert byte array to rune array +func toRunes(b []byte) []rune { + runes := make([]rune, 0, utf8.RuneCount(b)) + + for len(b) > 0 { + r, size := utf8.DecodeRune(b) + runes = append(runes, r) + + b = b[size:] + } + + return runes +} + +func sliceStart(slc []byte, index int) []byte { + len := len(slc) + i := 0 + totalSize := 0 + for totalSize < len { + if i >= index { + return slc[totalSize:] + } + + _, size := utf8.DecodeRune(slc[totalSize:]) + totalSize += size + i++ + } + + return slc[totalSize:] +} + +func sliceEnd(slc []byte, index int) []byte { + len := len(slc) + i := 0 + totalSize := 0 + for totalSize < len { + if i >= index { + return slc[:totalSize] + } + + _, size := utf8.DecodeRune(slc[totalSize:]) + totalSize += size + i++ + } + + return slc[:totalSize] +} + +// 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 { @@ -33,11 +84,7 @@ func NumOccurences(s string, c byte) int { // Spaces returns a string with n spaces func Spaces(n int) string { - var str string - for i := 0; i < n; i++ { - str += " " - } - return str + return strings.Repeat(" ", n) } // Min takes the min of two ints @@ -56,13 +103,20 @@ func Max(a, b int) int { 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 false + return true } c := str[0] return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') @@ -73,6 +127,16 @@ 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 { @@ -88,6 +152,18 @@ 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 := "" @@ -102,7 +178,7 @@ func GetLeadingWhitespace(str string) string { } // IsSpaces checks if a given string is only spaces -func IsSpaces(str string) bool { +func IsSpaces(str []byte) bool { for _, c := range str { if c != ' ' { return false @@ -112,6 +188,17 @@ func IsSpaces(str string) bool { 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) { @@ -140,32 +227,84 @@ func GetModTime(path string) (time.Time, bool) { return info.ModTime(), true } -func StringWidth(str string) int { +// StringWidth returns the width of a string where tabs count as `tabsize` width +func StringWidth(str string, tabsize int) int { sw := runewidth.StringWidth(str) - sw += NumOccurences(str, '\t') * (int(settings["tabsize"].(float64)) - 1) + 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 } -func WidthOfLargeRunes(str string) int { +// 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 = int(settings["tabsize"].(float64)) + 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 { @@ -173,3 +312,37 @@ func Abs(n int) int { } 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).") +} + +// 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) +} + +// GetPath returns a filename without everything following a `:` +// This is used for opening files like util.go:10:5 to specify a line and column +func GetPath(path string) string { + if strings.Contains(path, ":") { + path = strings.Split(path, ":")[0] + } + return path +}