14 "github.com/mattn/go-runewidth"
15 homedir "github.com/mitchellh/go-homedir"
18 // Util.go is a collection of utility functions that are used throughout
21 // Count returns the length of a string in runes
22 // This is exactly equivalent to utf8.RuneCountInString(), just less characters
23 func Count(s string) int {
24 return utf8.RuneCountInString(s)
27 // NumOccurrences counts the number of occurrences of a byte in a string
28 func NumOccurrences(s string, c byte) int {
30 for i := 0; i < len(s); i++ {
38 // Spaces returns a string with n spaces
39 func Spaces(n int) string {
40 return strings.Repeat(" ", n)
43 // Min takes the min of two ints
44 func Min(a, b int) int {
51 // Max takes the max of two ints
52 func Max(a, b int) int {
59 // FSize gets the size of a file
60 func FSize(f *os.File) int64 {
66 // IsWordChar returns whether or not the string is a 'word character'
67 // If it is a unicode character, then it does not match
68 // Word characters are defined as [A-Za-z0-9_]
69 func IsWordChar(str string) bool {
75 return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
78 // IsWhitespace returns true if the given rune is a space, tab, or newline
79 func IsWhitespace(c rune) bool {
80 return c == ' ' || c == '\t' || c == '\n'
83 // IsStrWhitespace returns true if the given string is all whitespace
84 func IsStrWhitespace(str string) bool {
85 for _, c := range str {
93 // Contains returns whether or not a string array contains a given string
94 func Contains(list []string, a string) bool {
95 for _, b := range list {
103 // Insert makes a simple insert into a string at the given position
104 func Insert(str string, pos int, value string) string {
105 return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
108 // MakeRelative will attempt to make a relative path between path and base
109 func MakeRelative(path, base string) (string, error) {
111 rel, err := filepath.Rel(base, path)
120 // GetLeadingWhitespace returns the leading whitespace of the given string
121 func GetLeadingWhitespace(str string) string {
123 for _, c := range str {
124 if c == ' ' || c == '\t' {
133 // IsSpaces checks if a given string is only spaces
134 func IsSpaces(str string) bool {
135 for _, c := range str {
144 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
145 func IsSpacesOrTabs(str string) bool {
146 for _, c := range str {
147 if c != ' ' && c != '\t' {
155 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
156 // as 'true' and 'false' respectively
157 func ParseBool(str string) (bool, error) {
164 return strconv.ParseBool(str)
167 // EscapePath replaces every path separator in a given path with a %
168 func EscapePath(path string) string {
169 path = filepath.ToSlash(path)
170 return strings.Replace(path, "/", "%", -1)
173 // GetModTime returns the last modification time for a given file
174 // It also returns a boolean if there was a problem accessing the file
175 func GetModTime(path string) (time.Time, bool) {
176 info, err := os.Stat(path)
178 return time.Now(), false
180 return info.ModTime(), true
183 // StringWidth returns the width of a string where tabs count as `tabsize` width
184 func StringWidth(str string, tabsize int) int {
185 sw := runewidth.StringWidth(str)
187 for _, ch := range str {
190 ts := tabsize - (lineIdx % tabsize)
202 // WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
203 // that have a width larger than 1 (this also counts tabs as `tabsize` width)
204 func WidthOfLargeRunes(str string, tabsize int) int {
207 for _, ch := range str {
210 w = tabsize - (lineIdx % tabsize)
212 w = runewidth.RuneWidth(ch)
226 // RunePos returns the rune index of a given byte index
227 // This could cause problems if the byte index is between code points
228 func runePos(p int, str string) int {
229 return utf8.RuneCountInString(str[:p])
232 func lcs(a, b string) string {
237 for i, r := range arunes {
238 if i >= len(brunes) {
250 // CommonSubstring gets a common substring among the inputs
251 func CommonSubstring(arr ...string) string {
254 for _, str := range arr[1:] {
255 commonStr = lcs(commonStr, str)
261 // Abs is a simple absolute value function for ints
262 func Abs(n int) int {
269 // FuncName returns the full name of a given function object
270 func FuncName(i interface{}) string {
271 return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
274 // ShortFuncName returns the name only of a given function object
275 func ShortFuncName(i interface{}) string {
276 return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
279 // SplitCommandArgs separates multiple command arguments which may be quoted.
280 // The returned slice contains at least one string
281 func SplitCommandArgs(input string) []string {
283 var curQuote *bytes.Buffer
285 curArg := new(bytes.Buffer)
288 finishQuote := func() {
292 str := curQuote.String()
293 if unquoted, err := strconv.Unquote(str); err == nil {
296 curArg.WriteString(str)
300 appendResult := func() {
304 str := curArg.String()
305 result = append(result, str)
309 for _, r := range input {
310 if r == ' ' && curQuote == nil {
314 appendRuneToBuff := func() {
316 curQuote.WriteRune(r)
323 if r == '"' && curQuote == nil {
324 curQuote = new(bytes.Buffer)
327 if curQuote != nil && !escape {
331 } else if r == '\\' {
349 // JoinCommandArgs joins multiple command arguments and quote the strings if needed.
350 func JoinCommandArgs(args ...string) string {
351 buf := new(bytes.Buffer)
353 for _, arg := range args {
359 quoted := strconv.Quote(arg)
360 if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
361 buf.WriteString(quoted)
370 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
371 // home directory. Does nothing if the path does not start with '~'.
372 func ReplaceHome(path string) string {
373 if !strings.HasPrefix(path, "~") {
377 home, err := homedir.Dir()
379 messenger.Error("Could not find home directory: ", err)
382 return strings.Replace(path, "~", home, 1)